Wednesday, January 16, 2008

C# Generics Performance Gain Benchmarks

Some of my readers may recall an earlier post I made (http://strainthebrain.blogspot.com/2007/12/c-generics-performance-gain-not-so-fast.html), talking about the lack of performance I was seeing during some testing with generics. Thanks to a reader, the mystery was clarified. When testing out generics, make sure that the data type you're declaring is a value type, not a reference type! If you need clarification on which are which (I can admit I did), refer here for value types and here for reference types.

I have put together some revised code, as well as implemented a better mechanism for benchmarking code execution performance using the diagnostics stopwatch.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace GenericsPerformance
{
    class Program
    {
        static void Main(string[] args)
        {
            int intVal = 0;
            string strVal = "";

            //Generics Test-------------------------------------------------------
            System.Diagnostics.Stopwatch genericSW = new System.Diagnostics.Stopwatch();

            genericSW.Start();
            Stack<int> genStack = new Stack<int>();
            for (int i = 0; i < 1000000; i++)
                genStack.Push(i);
            for (int i = 0; i < 1000000; i++)
                intVal = genStack.Pop();
            genericSW.Stop();

            Console.WriteLine("Generics (using int): {0} ms", genericSW.ElapsedMilliseconds);
            //--------------------------------------------------------------------

            //Non-generics Test---------------------------------------------------
            System.Diagnostics.Stopwatch nongenericSW = new System.Diagnostics.Stopwatch();

            nongenericSW.Start();
            Stack nonGenStack = new Stack();
            for (int i = 0; i < 1000000; i++)
                nonGenStack.Push(i);
            for (int i = 0; i < 1000000; i++)
                intVal = (int)nonGenStack.Pop();
            nongenericSW.Stop();

            Console.WriteLine("Non-Generics (using int): {0} ms", nongenericSW.ElapsedMilliseconds);
            //--------------------------------------------------------------------

            //Generics Test-------------------------------------------------------
            System.Diagnostics.Stopwatch genericSW2 = new System.Diagnostics.Stopwatch();

            genericSW2.Start();
            Stack<string> genStack2 = new Stack<string>();
            for (int i = 0; i < 1000000; i++)
                genStack2.Push(i.ToString());
            for (int i = 0; i < 1000000; i++)
                strVal = genStack2.Pop();
            genericSW2.Stop();

            Console.WriteLine("Generics (using string): {0} ms", genericSW2.ElapsedMilliseconds);
            //--------------------------------------------------------------------

            //Non-generics Test---------------------------------------------------
            System.Diagnostics.Stopwatch nongenericSW2 = new System.Diagnostics.Stopwatch();

            nongenericSW2.Start();
            Stack nonGenStack2 = new Stack();
            for (int i = 0; i < 1000000; i++)
                nonGenStack2.Push(i.ToString());
            for (int i = 0; i < 1000000; i++)
                strVal = (string)nonGenStack2.Pop();
            nongenericSW2.Stop();

            Console.WriteLine("Non-Generics (using string): {0} ms", nongenericSW2.ElapsedMilliseconds);
            //--------------------------------------------------------------------

            Console.ReadLine();
        }
    }
}
While the results varied slightly each run, I will show you 3 separate runs of this program. You'll notice that I first show Generics vs. Non-generics using a value type of int (this SHOULD perform better). I then show Generics vs. Non-generics using a reference type of string (Microsoft makes no claim on performance gain in this instance).

Run 1 Result:
Generics (using int): 49 ms
Non-Generics (using int): 212 ms
Generics (using string): 911 ms
Non-Generics (using string): 912 ms
Run 2 Result:
Generics (using int): 50 ms
Non-Generics (using int): 230 ms
Generics (using string): 935 ms
Non-Generics (using string): 944 ms
Run 3 Result:
Generics (using int): 50 ms
Non-Generics (using int): 212 ms
Generics (using string): 899 ms
Non-Generics (using string): 908 ms
As can be seen, generics using appropriate value types (such as int) show a dramatically improved performance gain, whereas generics using reference types (such as string) only perform slightly better than their non-generic counterpart.

8 comments:

Anonymous said...

I received slower times for Generic using strings than non-generics.

Chris Ball said...

That doesn't surprise me. Results will definitely vary when using strings with generics (or any reference type for that matter).

Anonymous said...

Try revising the test program so that all of the strings are built first. Then you are not timing calls to i.ToString(). My machine will give results along the lines of Build test strings : 1252 ms
Generics (using string): 147 ms
Non-Generics (using string): 209 ms
if I build a string[] first.

Anonymous said...

string is not a value type, this does not apply. although string is a native type it is still a reference type, the value type is a char array.

Anonymous said...

Your tests are wrong. Generics are much slower at operations then non generic methods or operations

Christopher M. Ball said...

Incorrect. This post confirms what Microsoft conveys, and that is that generics provide a slight performance edge since they do not require boxing/unboxing nor typecasting of the values.

Christopher M. Ball said...

And to the other Anonymous who felt compelled to repeat what is in my blog post, I explicitly stated that "string" is a reference type and that MS made no statements regarding performance gains for reference types. The "string" type was merely added to the test for information, not confirmation of performance.

The STORK said...

I've found that C# generic arrays are actually far faster than STL vectors in C++ and are not far behind the performance of native code arrays. Check this out:

http://www.treatyist.com/issue1/cpp_vs_csharp_arrays.aspx