Generic 'new T()' Performance Issue and a Solution

by Moridin8 11. June 2009 00:00

Generics is one of the best things to ever feature in the CLR.  The advantages of Generics feature are myriad and to be quite honest, I couldn’t imagine life without it.  It is efficient, fast and flexible with few exceptions.  However, one of those exceptions is in the area of Dynamic type instantiation:

 T tmp = new T();

This instruction is syntax sugar for the following:

 T tmp = Activator.CreateInstance<T>();

The reason Activator is used, is because the CLR can not instantiate an unknown type (the CLR uses the CIL instruction ‘NewObj’ for this task.)

The problem with Activator.CreateInstance, is it is so VERY slow.  Around 95 TIMES slower than a simple ‘new’.  

That’s quite a figure!  And in some cases highly unacceptable – like in a couple of projects I have been drafted into in an attempt to identify and enhance performance.  Sometimes, throwing new Hardware at an application can only take you so far.

One such application I worked on heavily used ‘new T()’ syntax for reasons beyond this text.  What is more poignant is that the hardware was purchased, the software was deployed (although not published) and was failing SLA targets under moderate load (there were other issues, but again, not important here).   After a little analysis and a quick set of tests, I confidently told them that I could increase the speed of their software between 500 to 800% with very little work.

Interestingly the development team on the project didn’t believe me and tried to call my bluff.  Two days later and a little bit of find-and-replace I was able to demonstrate the principle.  The code actually ran at about 850% faster... 

I now wish I had bet some big dollar cash on it....  :(

Essentially the solution lies in the abilities of Reflection and Delegates to do some very flexible run-time code generation and execution.  But rather than explain it I’ll let the example code below do the explaining for me.

If I have more time in the next day or so, I will maybe flesh out the explanation. Here follows an example of proof of concept I produced along side the two day work load....

Here are the stats I quickly generated a few minutes ago (Code follows).  Quickest methods first (green), Dynamic methods second (Yellow) and the usual 'new T()' last.    Please note that the 'D1' and 'D2'  examples also use the Dynamic code examples, however they are not very flexible:  You can't always get everything you want ;)

--

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;

namespace ConsoleApplication2
{
    internal delegate object DynamicActivator();

    internal abstract class SomeBaseClass { }
    internal class SomeClassA : SomeBaseClass { }
    internal class SomeClassB : SomeBaseClass { }

    internal class SomeFactory
    {
        private const BindingFlags BFLAGS = BindingFlags.Instance
                                            | BindingFlags.Public
                                            | BindingFlags.NonPublic
                                            | BindingFlags.ExactBinding;

        private static readonly Dictionary<Type, DynamicActivator> createSomeClass;

        public static readonly DynamicActivator SomeClassA_D1;
        public static readonly DynamicActivator SomeClassB_D2;

        static SomeFactory()
        {
            createSomeClass = new Dictionary<Type, DynamicActivator>();

            // boot-strap the DynamicActivators for test
            SomeClassA_D1 = DynamicNew<SomeClassA>();
            SomeClassB_D2 = DynamicNew<SomeClassB>();

            CacheDynamicActivator<SomeClassA>();
            CacheDynamicActivator<SomeClassB>();
        }

        public static SomeClassA SomeClassA()
        {
            return new SomeClassA();
        }

        public static SomeClassB SomeClassB()
        {
            return new SomeClassB();
        }

        // B1 and B2 are the same
        public static T SomeClassB1<T>()
            where T : SomeBaseClass, new()
        {
            return new T(); // compiles to "Activator.CreateInstance<T>()"
        }

        public static T SomeClassB2<T>()
            where T : SomeBaseClass, new()
        {
            return Activator.CreateInstance<T>();
        }

        // This version for JIT caching
        public static T SomeClassC1<T>()
            where T : SomeBaseClass, new()
        {
            CacheDynamicActivator<T>();

            return (T)createSomeClass[typeof(T)]();
        }

        // The DynamicActivator solution to 'new T()'
        public static T SomeClassC2<T>()
            where T : SomeBaseClass, new()
        {
            return (T)createSomeClass[typeof(T)]();
        }

        // Caches the DynamicActivator.
        private static void CacheDynamicActivator<T>()
            where T : class, new()
        {
            Type TT = typeof(T);

            if (!createSomeClass.ContainsKey(TT))
                createSomeClass.Add(TT, DynamicNew<T>());
        }

        // Generates a Dynamic method that can act as a more performant version of 'new T();'
        // 
        // Note: this is a slow operation, hence the bootstrap or JIT generation.
        // <typeparam name="T">Class to create</typeparam>
        // <returns>delegate for invocation</returns>
        private static DynamicActivator DynamicNew<T>()
            where T : class, new()
        {
            Type TT = typeof(T);

            DynamicMethod dm = new DynamicMethod(Guid.NewGuid().ToString(), TT, null, TT.Module);

            ILGenerator il = dm.GetILGenerator();

            il.Emit(OpCodes.Newobj, TT.GetConstructor(BFLAGS, null, Type.EmptyTypes, null));
            il.Emit(OpCodes.Ret);

            return (DynamicActivator)dm.CreateDelegate(typeof(DynamicActivator));
        }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            const int len = 10000000;

            SomeClassA tmpA;
            SomeClassB tmpB;

            Thread.Sleep(2000);

            Stopwatch sp = new Stopwatch();

            for (int j = 0; j < 1; j++)
            {
                Console.WriteLine();

                sp.Reset(); sp.Start();
                for (int i = 0; i < len; i++)
                    tmpA = new SomeClassA();
                sp.Stop();
                Console.WriteLine("                      new SomeClassA() = {0}", sp.Elapsed);

                sp.Reset(); sp.Start();
                for (int i = 0; i < len; i++)
                    tmpA = SomeFactory.SomeClassA();
                sp.Stop();
                Console.WriteLine("              SomeFactory.SomeClassA() = {0}", sp.Elapsed);

                sp.Reset(); sp.Start();
                for (int i = 0; i < len; i++)
                    tmpA = SomeFactory.SomeClassB1<SomeClassA>();
                sp.Stop();
                Console.WriteLine(" SomeFactory.SomeClassB1<SomeClassA>() = {0}", sp.Elapsed);

                sp.Reset(); sp.Start();
                for (int i = 0; i < len; i++)
                    tmpA = SomeFactory.SomeClassB2<SomeClassA>();
                sp.Stop();
                Console.WriteLine(" SomeFactory.SomeClassB2<SomeClassA>() = {0}", sp.Elapsed);

                sp.Reset(); sp.Start();
                for (int i = 0; i < len; i++)
                    tmpA = SomeFactory.SomeClassC1<SomeClassA>();
                sp.Stop();
                Console.WriteLine(" SomeFactory.SomeClassC1<SomeClassA>() = {0}", sp.Elapsed);

                sp.Reset(); sp.Start();
                for (int i = 0; i < len; i++)
                    tmpA = SomeFactory.SomeClassC1<SomeClassA>();
                sp.Stop();
                Console.WriteLine(" SomeFactory.SomeClassC2<SomeClassA>() = {0}", sp.Elapsed);

                sp.Reset(); sp.Start();
                for (int i = 0; i < len; i++)
                    tmpA = SomeFactory.SomeClassA_D1() as SomeClassA;
                sp.Stop();
                Console.WriteLine("           SomeFactory.SomeClassA_D1() = {0}", sp.Elapsed);

                Console.WriteLine();

                sp.Reset(); sp.Start();
                for (int i = 0; i < len; i++)
                    tmpB = new SomeClassB();
                sp.Stop();
                Console.WriteLine("                      new SomeClassB() = {0}", sp.Elapsed);

                sp.Reset(); sp.Start();
                for (int i = 0; i < len; i++)
                    tmpB = SomeFactory.SomeClassB();
                sp.Stop();
                Console.WriteLine("              SomeFactory.SomeClassB() = {0}", sp.Elapsed);

                sp.Reset(); sp.Start();
                for (int i = 0; i < len; i++)
                    tmpB = SomeFactory.SomeClassB1<SomeClassB>();
                sp.Stop();
                Console.WriteLine(" SomeFactory.SomeClassB1<SomeClassB>() = {0}", sp.Elapsed);

                sp.Reset(); sp.Start();
                for (int i = 0; i < len; i++)
                    tmpB = SomeFactory.SomeClassB1<SomeClassB>();
                sp.Stop();
                Console.WriteLine(" SomeFactory.SomeClassB2<SomeClassB>() = {0}", sp.Elapsed);

                sp.Reset(); sp.Start();
                for (int i = 0; i < len; i++)
                    tmpB = SomeFactory.SomeClassC1<SomeClassB>();
                sp.Stop();
                Console.WriteLine(" SomeFactory.SomeClassC1<SomeClassB>() = {0}", sp.Elapsed);

                sp.Reset(); sp.Start();
                for (int i = 0; i < len; i++)
                    tmpB = SomeFactory.SomeClassC1<SomeClassB>();
                sp.Stop();
                Console.WriteLine(" SomeFactory.SomeClassC2<SomeClassB>() = {0}", sp.Elapsed);

                sp.Reset(); sp.Start();
                for (int i = 0; i < len; i++)
                    tmpB = SomeFactory.SomeClassB_D2() as SomeClassB;
                sp.Stop();
                Console.WriteLine("           SomeFactory.SomeClassB_D2() = {0}", sp.Elapsed);
            }

            Console.ReadLine();
        }
    }
}

--

 

Tags: , , , , ,

Articles

Comments

6/17/2009 12:37:43 PM #

Leadership Styles

I have to say... I have never seen a bluey purply blog like this in my life! Haha, funny how such a small tweak in code can make such a unique site.

Leadership Styles United Kingdom | Reply

6/17/2009 4:50:48 PM #

Moridin8

"Unique" huh?  *haha*

Moridin8 United States | Reply

6/25/2009 6:21:32 AM #

its my net history

           I am newbie in creating blog using the latest version of blogengine. Many of the tutorials i tried and finally i got many information in creating a blog or website. So i decided to create a new one which i can represent about what i got information in many tutorials. But as of now i want to know the problems we incurred in creating a blog site. Thanks for the information. I want to add this one.

its my net history United States | Reply

7/22/2009 9:01:48 AM #

NubCake

Thanks! This was really useful. Anyway, I think there is a small typo for the example C2 that pre-caches the type classes into the dictionary.

tmpA = SomeFactory.SomeClassC1<SomeClassA>();
tmpB = SomeFactory.SomeClassC1<SomeClassB>();

should be:

tmpA = SomeFactory.SomeClassC2<SomeClassA>();
tmpB = SomeFactory.SomeClassC2<SomeClassB>();

By doing so, the performance of the pre-caching method (C2) shows up better than the JIT method (C1), which is pretty logical. Here are my results based on your timing:

Direct ~ 0.15 sec
Bootstrapping ~ 0.2 sec (D1,D2)
Dictionary pre-caching ~ 0.8 sec (C2)
Dictionary JIT ~ 1.4 sec (C1)

The bootstrapping method looks kinda ugly but I'm using it. The overhead for the dictionary is pretty unbearable in my case where only a handful of classes A and B require dynamic activation.

In any case, it works. Thanks.

NubCake Singapore | Reply

10/30/2009 2:46:28 PM #

cash loans

I like what I see. keep it going

cash loans United States | Reply

11/27/2009 9:50:33 AM #

payday loans

Thank you for your help!

payday loans United States | Reply

11/30/2009 11:43:34 PM #

payday loans

Just try to smile for about 2-3 mins then you can get back to work

payday loans United States | Reply

Add comment




  Country flag

biuquote
  • Comment
  • Preview
Loading



Powered by BlogEngine.NET 1.5.0.7

About Matt R.Warren

MeMy name is Matt and I am the current tenant of this small corner of the internet. I mostly architect, design and prototype applications that use .NET with C# and a little C++/CLI for Enterprise although I am aware of and enjoy fully embracing Java based solutions and alternatives such as Mono/Linux.  

I have worked on projects ranging from small tools to large distributed real-time Enterprise systems ranging from EPOS and real-time/JIT stock management systems, to distributed applications for National/International Utility, Healthcare, Insurance and Finance  in the private sector in both the USA and the EU.

My LinkedIn Profile (Opens new window/tab)

“Matt is one of the brightest people I've worked with. His in-depth knowledge of the .NET frameworks has been a tremendous benefit to nVISIA and our clients. His knowledge of software architecture in general allows him to architect systems for the best fit to his client's needs.” 
Dan Christopherson , Technical Director , nVISIA

“I had the distinct pleasure of working with Matt at nVisia. Matt's understanding of the Microsoft Technical space is outstanding. He is constantly working on improving his technical skills and rapidly masters any new technology that he encounters. He is an excellent teacher and a wonderful asset for any size team.” 
Jim Harnden , Senior Technical Architect , nVISIA

“Matt Warren is a very talented developer with great capacity for self study, investigation and adapts to new languages and frameworks with ease. He has an excellent grasp of software architecture and modern development principles. He has proven himself time and time again to be a hard worker and someone who can get the job done when you're in a tight spot.” 
Andrew Jump , Partner, C# Developer , Contegra

This website represents some of my spare time.  My small presence on the web between my family and my career.  I hope over time you find many useful things here.