Friday 15 August 2014

Value type vs Reference Type - C# performance comparison

In this post, I will talk about the main difference between the value type and the reference type in C# and how each of them can impact the performance of your program. 
I will demonstrate it with a concrete case where the performance ratio is around 1000%. 

You can find here a quick introduction ( FYI : the link also says that the type "String" is a reference type rather than a value type).
-Value types are bool, byte,char, decimal, double, enum, float, int, long, sbyte,short, struct, uint, ulong , ushort.
-Reference types are String, class,All arrays (even if their elements are value types),Delegates.

.A data type is a value type if it holds the data within its own memory allocation
.A reference type contains a pointer to another memory location that holds the data
When we pass an object as an input to a function, the notion of value/reference type is also important.

1-void PerformOp(ValueType input)
2-void PerformOp(ReferenceType input)

When calling the function 1, the value type input is stored in a special region of the memory called Stack. Stack region are fully managed by the CPU and we don't care of any memory allocation task. The Stack performs a First Into Last Out mode. At the exit of this function, ALL of variable are popped out of the stack.

When calling the function 2, the reference type input is stored in an another region called Heap.It's another region of the memory where the access is slightly slower than IO on the Stack, because the program need to use pointer ( or address reference) to access the content referenced by this address.

At this point, we have seen the main difference between a value/reference type and program should perform quickly if we use value type.
Is it really the case ? Yes, I can say that using a value type is really mandatory if I need to perform a heavy operation.

In this demo, I use 2 objects : MyClass  and MyStruct.I put all the program at the end of this post ( for now you can ignore the content )
Those objects have the same properties. The only difference is that MyClass is a normal class ( so a reference type ) and MyStruct is a struct ( value type).
The program contains a function FillValueType(MyStruct) and FillReference(MyClass) to build a mock object of each type by filling all the properties by a random values.
There also 2 functions named PerformOpOnHeap(MyClass) and PerformOpOnStack(MyStruct).

I use 2 parallel threads inside which I put a stopwatch to measure N iterations call of  PerformOpOnHeap and PerformOpOnStack.Both functions do exactly the same stuff.


Metric :


Conclusion :
As shown in the diagram, the manipulation of a value type is better than working with a reference value. As from 1000 items, it is a better choice to find a way to transform a class to a struct equivalent and then work with the struct , back again to the class at the end of the heavy operation.

In industry, during a financial project where a lot of process should be performed against a huge list of elements, I often used this trick.

Despite of this result, you must know that the memory stack is limited in size so you have to take care of how the function should be optimized. Remember that when the function exits, all associated stack data are popped out. So it may be better to call the function one or more time rather than having the whole process executed once to avoid a stack memory exception.

Full source code used in this post :
using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading.Tasks;

namespace ClassVsStruct
{
    class Program
    {
        static void Main(string[] args)
        {

            var dMyStruct = new MyStruct();
            var dFilledStruct = FillValueType(dMyStruct);

            var dMyClass = new MyClass();
            FillReference(dMyClass);
            //change the loops value
            var loops = 0;
            Task.Factory.StartNew(() =>
            {
                var ws = new Stopwatch();
                ws.Start();
                Console.WriteLine("Start task PerformOpOnHeap");
                for (var i = 0; i < loops; i++)
                {                   
                    PerformOpOnHeap(dMyClass);                 
                }
                ws.Stop();
                Console.WriteLine("PerformOpOnHeap ends after : " + ws.ElapsedMilliseconds + " ms"); 
            });

            Task.Factory.StartNew(() =>
            {
                var ws = new Stopwatch();
                ws.Start();
                Console.WriteLine("Start task PerformOpOnStack");
                for (var i = 0; i < loops; i++)
                {
                    PerformOpOnStack(dMyStruct);                  
                }
                ws.Stop();
                Console.WriteLine("PerformOpOnStack ends after : " + ws.ElapsedMilliseconds + " ms");
            });
          
         
            Console.WriteLine("End");

            Console.ReadLine();
        }

        /// <summary>
        /// Return a random string
        /// </summary>
        /// <returns></returns>
        static private string GenerateString()
        {
            return Guid.NewGuid().ToString();
        }

        static MyStruct FillValueType(object obj)
        {
            var objType = obj.GetType();
            var i =0;
            foreach (var propertyInfo in objType.GetProperties())
            {
                i++;
                if (propertyInfo.PropertyType == typeof(string))
                {
                  
                        obj.GetType()
                            .GetProperty(propertyInfo.Name)
                            .SetValue(obj, GenerateString(), BindingFlags.GetProperty, null, null, null);
                   
                }
             
                else if (propertyInfo.PropertyType == typeof(DateTime?))
                {
                    obj.GetType()
                       .GetProperty(propertyInfo.Name)
                       .SetValue(obj, DateTime.Now, BindingFlags.GetProperty, null, null, null);
                }
                else if (propertyInfo.PropertyType == typeof(double?))
                {
                    obj.GetType()
                       .GetProperty(propertyInfo.Name)
                       .SetValue(obj, 1565498798.8978D, BindingFlags.GetProperty, null, null, null);
                }
            }
            Console.WriteLine("Number of loop " + i);
            return (MyStruct)obj;
        }

        static void FillReference(object obj)
        {
            var objType = obj.GetType();
            var i = 0;
            foreach (var propertyInfo in objType.GetProperties())
            {
                i++;
                if (propertyInfo.PropertyType == typeof(string))
                {

                    obj.GetType()
                        .GetProperty(propertyInfo.Name)
                        .SetValue(obj, GenerateString(), BindingFlags.GetProperty, null, null, null);

                }

                else if (propertyInfo.PropertyType == typeof(DateTime?))
                {
                    obj.GetType()
                       .GetProperty(propertyInfo.Name)
                       .SetValue(obj, DateTime.Now, BindingFlags.GetProperty, null, null, null);
                }
                else if (propertyInfo.PropertyType == typeof(double?))
                {
                    obj.GetType()
                       .GetProperty(propertyInfo.Name)
                       .SetValue(obj, 1565498798.8978D, BindingFlags.GetProperty, null, null, null);
                }
            }
            Console.WriteLine("Number of loop " + i);
        }

        static MyStruct PerformOpOnStack(MyStruct sStruct)
        {
            var objType = sStruct.GetType();
           
            foreach (var propertyInfo in objType.GetProperties())
            {
                if (propertyInfo.PropertyType == typeof (string))
                {
                    var pProperty = sStruct.GetType()
                        .GetProperty(propertyInfo.Name);
                    var currentValue =
                        pProperty.GetValue(sStruct, BindingFlags.Public, null, null, null);
                    pProperty
                        .SetValue(sStruct, currentValue+GenerateString()+"b", BindingFlags.GetProperty, null, null, null);
                }

                else if (propertyInfo.PropertyType == typeof(DateTime?))
                {
                    sStruct.GetType()
                       .GetProperty(propertyInfo.Name)
                       .SetValue(sStruct, DateTime.Now, BindingFlags.GetProperty, null, null, null);
                }
                else if (propertyInfo.PropertyType == typeof(double?))
                {
                    sStruct.GetType()
                       .GetProperty(propertyInfo.Name)
                       .SetValue(sStruct, 1565498798.8978D, BindingFlags.GetProperty, null, null, null);
                }
            }
           
            return sStruct;
        }

        static void PerformOpOnHeap(MyClass cClass)
        {
            var objType = cClass.GetType();
         
            foreach (var propertyInfo in objType.GetProperties())
            {
                if (propertyInfo.PropertyType == typeof(string))
                {
                    var pProperty = cClass.GetType()
                        .GetProperty(propertyInfo.Name);
                    var currentValue =
                        pProperty.GetValue(cClass, BindingFlags.Public, null, null, null);
                    pProperty
                        .SetValue(cClass, currentValue + GenerateString() + "b", BindingFlags.GetProperty, null, null, null);
                }
                else if (propertyInfo.PropertyType == typeof(DateTime?))
                {
                    cClass.GetType()
                       .GetProperty(propertyInfo.Name)
                       .SetValue(cClass, DateTime.Now, BindingFlags.GetProperty, null, null, null);
                }
                else if (propertyInfo.PropertyType == typeof(double?))
                {
                    cClass.GetType()
                       .GetProperty(propertyInfo.Name)
                       .SetValue(cClass, 1565498798.8978D, BindingFlags.GetProperty, null, null, null);
                }
            }
         
        }
    }

    public class MyClass
    {

        public string SuperPropertyString1 { get; set; }
        public string SuperPropertyString2 { get; set; }
        public string SuperPropertyString3 { get; set; }
        public string SuperPropertyString4 { get; set; }
        public string SuperPropertyString5 { get; set; }
        public string SuperPropertyString6 { get; set; }
        public string SuperPropertyString7 { get; set; }
        public string SuperPropertyString8 { get; set; }
        public string SuperPropertyString9 { get; set; }
        public string SuperPropertyString10 { get; set; }
        public string SuperPropertyString11 { get; set; }
        public string SuperPropertyString12 { get; set; }
        public string SuperPropertyString13 { get; set; }
        public string SuperPropertyString14 { get; set; }
        public string SuperPropertyString15 { get; set; }
        public string SuperPropertyString16 { get; set; }
        public string SuperPropertyString17 { get; set; }
        public string SuperPropertyString18 { get; set; }
        public string SuperPropertyString19 { get; set; }
        public string SuperPropertyString20 { get; set; }

        public DateTime? SuperPropertyDateTime1 { get; set; }
        public DateTime? SuperPropertyDateTime2 { get; set; }
        public DateTime? SuperPropertyDateTime3 { get; set; }
        public DateTime? SuperPropertyDateTime4 { get; set; }
        public DateTime? SuperPropertyDateTime5 { get; set; }
        public DateTime? SuperPropertyDateTime6 { get; set; }
        public DateTime? SuperPropertyDateTime7 { get; set; }
        public DateTime? SuperPropertyDateTime8 { get; set; }
        public DateTime? SuperPropertyDateTime9 { get; set; }
        public DateTime? SuperPropertyDateTime10 { get; set; }
        public DateTime? SuperPropertyDateTime11 { get; set; }
        public DateTime? SuperPropertyDateTime12 { get; set; }
        public DateTime? SuperPropertyDateTime13 { get; set; }
        public DateTime? SuperPropertyDateTime14 { get; set; }
        public DateTime? SuperPropertyDateTime15 { get; set; }
        public DateTime? SuperPropertyDateTime16 { get; set; }
        public DateTime? SuperPropertyDateTime17 { get; set; }
        public DateTime? SuperPropertyDateTime18 { get; set; }
        public DateTime? SuperPropertyDateTime19 { get; set; }
        public DateTime? SuperPropertyDateTime20 { get; set; }


        public double? SuperPropertyDouble1 { get; set; }
        public double? SuperPropertyDouble2 { get; set; }
        public double? SuperPropertyDouble3 { get; set; }
        public double? SuperPropertyDouble4 { get; set; }
        public double? SuperPropertyDouble5 { get; set; }
        public double? SuperPropertyDouble6 { get; set; }
        public double? SuperPropertyDouble7 { get; set; }
        public double? SuperPropertyDouble8 { get; set; }
        public double? SuperPropertyDouble9 { get; set; }
        public double? SuperPropertyDouble10 { get; set; }
        public double? SuperPropertyDouble11 { get; set; }
        public double? SuperPropertyDouble12 { get; set; }
        public double? SuperPropertyDouble13 { get; set; }
        public double? SuperPropertyDouble14 { get; set; }
        public double? SuperPropertyDouble15 { get; set; }
        public double? SuperPropertyDouble16 { get; set; }
        public double? SuperPropertyDouble17 { get; set; }
        public double? SuperPropertyDouble18 { get; set; }
        public double? SuperPropertyDouble19 { get; set; }
        public double? SuperPropertyDouble20 { get; set; }



    }
    
    public struct MyStruct
    {
       
        public string SuperPropertyString1 { get; set; }
        public string SuperPropertyString2 { get; set; }
        public string SuperPropertyString3 { get; set; }
        public string SuperPropertyString4 { get; set; }
        public string SuperPropertyString5 { get; set; }
        public string SuperPropertyString6 { get; set; }
        public string SuperPropertyString7 { get; set; }
        public string SuperPropertyString8 { get; set; }
        public string SuperPropertyString9 { get; set; }
        public string SuperPropertyString10 { get; set; }
        public string SuperPropertyString11 { get; set; }
        public string SuperPropertyString12 { get; set; }
        public string SuperPropertyString13 { get; set; }
        public string SuperPropertyString14 { get; set; }
        public string SuperPropertyString15 { get; set; }
        public string SuperPropertyString16 { get; set; }
        public string SuperPropertyString17 { get; set; }
        public string SuperPropertyString18 { get; set; }
        public string SuperPropertyString19 { get; set; }
        public string SuperPropertyString20 { get; set; }

        public DateTime? SuperPropertyDateTime1 { get; set; }
        public DateTime? SuperPropertyDateTime2 { get; set; }
        public DateTime? SuperPropertyDateTime3 { get; set; }
        public DateTime? SuperPropertyDateTime4 { get; set; }
        public DateTime? SuperPropertyDateTime5 { get; set; }
        public DateTime? SuperPropertyDateTime6 { get; set; }
        public DateTime? SuperPropertyDateTime7 { get; set; }
        public DateTime? SuperPropertyDateTime8 { get; set; }
        public DateTime? SuperPropertyDateTime9 { get; set; }
        public DateTime? SuperPropertyDateTime10 { get; set; }
        public DateTime? SuperPropertyDateTime11 { get; set; }
        public DateTime? SuperPropertyDateTime12 { get; set; }
        public DateTime? SuperPropertyDateTime13 { get; set; }
        public DateTime? SuperPropertyDateTime14 { get; set; }
        public DateTime? SuperPropertyDateTime15 { get; set; }
        public DateTime? SuperPropertyDateTime16 { get; set; }
        public DateTime? SuperPropertyDateTime17 { get; set; }
        public DateTime? SuperPropertyDateTime18 { get; set; }
        public DateTime? SuperPropertyDateTime19 { get; set; }
        public DateTime? SuperPropertyDateTime20 { get; set; }


        public double? SuperPropertyDouble1 { get; set; }
        public double? SuperPropertyDouble2 { get; set; }
        public double? SuperPropertyDouble3 { get; set; }
        public double? SuperPropertyDouble4 { get; set; }
        public double? SuperPropertyDouble5 { get; set; }
        public double? SuperPropertyDouble6 { get; set; }
        public double? SuperPropertyDouble7 { get; set; }
        public double? SuperPropertyDouble8 { get; set; }
        public double? SuperPropertyDouble9 { get; set; }
        public double? SuperPropertyDouble10 { get; set; }
        public double? SuperPropertyDouble11 { get; set; }
        public double? SuperPropertyDouble12 { get; set; }
        public double? SuperPropertyDouble13 { get; set; }
        public double? SuperPropertyDouble14 { get; set; }
        public double? SuperPropertyDouble15 { get; set; }
        public double? SuperPropertyDouble16 { get; set; }
        public double? SuperPropertyDouble17 { get; set; }
        public double? SuperPropertyDouble18 { get; set; }
        public double? SuperPropertyDouble19 { get; set; }
        public double? SuperPropertyDouble20 { get; set; }



    }
}




No comments:

Post a Comment