Saturday, November 14, 2009

Activator.CreateInstance(...) Performance



Recently I have been looking into performance profiling different methods of implementing an
 Object Factory pattern
.  Something that can choose an appropriate concrete type and create it when a requested interface is given.  
A fundamental portion of this is to be able to register types with the factory, and in my case this needs to be done by a xml configuration file.
 Therefore the type is ultimately created by calling Type.GetType("FullyQualifiedTypeNameHere, AssemblyName").  This has bothered
me for some time and I have been waiting until I have time to performance test it.




I have an existing Object Factory that can create types when a requested interface is given.  You can register a concrete type against
an interface either by code or configuration, but mostly this needs to be done by the configuration xml file.  I wrote a test program
to time different methods of creating objects and was a little surprised by the results.




Here's the test program code:


namespace ActivatorCreateInstanceAlternatives {
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Reflection;
    public class Program {
        private static ConstructorInfo _constr;
        private static Type _type;
        private static Dictionary<Guid, Client> allClients = new Dictionary<Guid, Client>();
        public static void Main(string[] args) {
            Console.WriteLine("Test One - Loop thru each 10000 times (x,x,x,x,x.... y,y,y,y,y.... z,z,z,z....).");
            var objectFactoryTime = ObjectFactoryPerfTests();
            var newCreateTime = NewPerfTests();
            var activatorTime = ActivatorCreatePerfTests();
            var activatorCachedTime = ActivatorCreateCachedTypePerfTests();
            var cachedConstructorTime = CachedConstructorPerfTests();

            Console.WriteLine("Baseline 'new' construct " + newCreateTime.ToString("N"));
            Console.WriteLine("Activator.CreateInstance " + activatorTime.ToString("N"));
            Console.WriteLine("Cached Activator.Create  " + activatorCachedTime.ToString("N"));
            Console.WriteLine("Cached constructor       " + cachedConstructorTime.ToString("N"));
            Console.WriteLine("Object Factory           " + objectFactoryTime.ToString("N"));
            Console.WriteLine(" ");
            Console.WriteLine("Test Two - For each 10,000 use each creation method (x,y,z, x,y,z, ...)");

            var objectFactoryTime2 = new List<double>();
            var newCreateTime2 = new List<double>();
            var activatorTime2 = new List<double>();
            var activatorCachedTime2 = new List<double>();
            var cachedConstructorTime2 = new List<double>();

            for (int index = 0; index < 10000; index++) {
                newCreateTime2.Add(NewPerfTest());
                activatorTime2.Add(ActivatorCreatePerfTest());
                activatorCachedTime2.Add(ActivatorCreateCachedTypePerfTest());
                cachedConstructorTime2.Add(CachedConstructorPerfTest());
                objectFactoryTime2.Add(ObjectFactoryPerfTest());
            }

            Console.WriteLine("Baseline 'new' construct " + newCreateTime2.Average(x => x).ToString("N"));
            Console.WriteLine("Activator.CreateInstance " + activatorTime2.Average(x => x).ToString("N"));
            Console.WriteLine("Cached Activator.Create  " + activatorCachedTime2.Average(x => x).ToString("N"));
            Console.WriteLine("Cached constructor       " + cachedConstructorTime2.Average(x => x).ToString("N"));
            Console.WriteLine("Object Factory           " + objectFactoryTime2.Average(x => x).ToString("N"));
            Console.WriteLine(" ");
        }

        private static double NewPerfTests() {
            var results = new List<double>();
            for (int index = 0; index < 10000; index++) {
                results.Add(NewPerfTest());
            }

            return results.Average(x => x);
        }

        private static double NewPerfTest() {
            var stopwatch = Stopwatch.StartNew();
            var client = new Client();
            Debug.Assert(client != null);
            stopwatch.Stop();
            allClients.Add(client.Id, client);
            return stopwatch.ElapsedTicks;
        }

        private static double ActivatorCreateCachedTypePerfTests() {
            var results = new List<double>();
            if (_type == null) {
                _type = Type.GetType("ActivatorCreateInstanceAlternatives.Client");
           }

            for (int index = 0; index < 10000; index++) {
                results.Add(ActivatorCreateCachedTypePerfTest());
            }

            return results.Average(x => x);
        }

        private static double ActivatorCreateCachedTypePerfTest() {
           var stopwatch = Stopwatch.StartNew();
           var client = Activator.CreateInstance(_type) as Client;
           Debug.Assert(client != null);
           stopwatch.Stop();
           allClients.Add(client.Id, client);
           return stopwatch.ElapsedTicks;
        }

        private static double ActivatorCreatePerfTests() {
            var results = new List<double>();
            for (int index = 0; index < 10000; index++) {
               results.Add(ActivatorCreatePerfTest());
            }

            return results.Average(x => x);
        }

        private static double ActivatorCreatePerfTest() {
            var stopwatch = Stopwatch.StartNew();
            var client = Activator.CreateInstance(Type.GetType("ActivatorCreateInstanceAlternatives.Client")) as Client;
            Debug.Assert(client != null);
            stopwatch.Stop();
            allClients.Add(client.Id, client);
            return stopwatch.ElapsedTicks;
        }

        private static double CachedConstructorPerfTests() {
            var results = new List<double>();
            if (_constr == null) {
                Type type = Type.GetType("ActivatorCreateInstanceAlternatives.Client");
                _constr = type.GetConstructor(new Type[] { });
            }

            Debug.Assert(_constr != null);
            for (int index = 0; index < 10000; index++) {
                results.Add(CachedConstructorPerfTest());
            }

            return results.Average(x => x);
        }

        private static double CachedConstructorPerfTest() {
            var stopwatch = Stopwatch.StartNew();
            var client = _constr.Invoke(new object[] { }) as Client;
            Debug.Assert(client != null);
            stopwatch.Stop();
            allClients.Add(client.Id, client);
            return stopwatch.ElapsedTicks;
        }

        private static double ObjectFactoryPerfTests() {
            var results = new List<double>();
            for (int index = 0; index < 10000; index++) {
                results.Add(ObjectFactoryPerfTest());
            }

            return results.Average(x => x);
        }

        private static double ObjectFactoryPerfTest() {
            var stopwatch = Stopwatch.StartNew();
            var client = ObjectFactory.Current.CreateInstance<IClient>() as Client;
            Debug.Assert(client != null);
            stopwatch.Stop();
            allClients.Add(client.Id, client);
            return stopwatch.ElapsedTicks;
        }
    }
 }




Test Overview
Let me summerise this code into what I am trying to profile.  
There are two test sets, each will create 10,000 objects using each technique of creating an object.  
The first test set creates 10,000 using one technique then moves onto the next technique.  
The second set creates one object using each technique 10,000 times. This is just to check consistency of the times.




New keyword
The 'new' keyword is used as a baseline control for the test, I wouldn't expect anything to be consistently faster than this.

Activator.CreateInstance
The next test is Activator.CreateInstance(Type.GetType("SomeTypeNameHere")).  
This I am expecting to be consistently the slowest, as it has to find the type then create an instance of it for each of the 10,000 iterations.

Activator.CreateInstance with cached Type
This test will use Activator.CreateInstance without refetching the type each iteration (the type is cached into a class level field).

Cached Constructor
This test will simply get a ConstructorInfo object, cache it into a class level field and Invoke for each iteration through the loop.

Object Factory
This is my existing object factory.  This isn't really a fair comparison as the object factory is doing a little more work than
simply creating the object, (like argument preconditions etc) but it shouldn't be a million miles off the others.

Running the program 3 times I get the following results:
Run1:
Run2:
Run3:

Summary of results:
 Baseline 'new' construct 5.26ms 1
 Activator Type cached 5.72ms 2
 Cached Constructor 9.23ms 3
 Object Factory 9.57ms 4
 Activator without caching the type 26.43 5
Its safe to say (despite the slightly high results from run1) that using the 'new' keyword to create an instance of a known type is the fastest, as you would expect.

But in second place is Activator.CreateInstance when using an instance of a known Type cached into a class level field.  So, because my object factory uses this technique already (with the addition of some checks etc) it is not that far behind. 

Conclusion
I don't think my existing Object Factory is broken.  This proves the cached type when using Activator.Create is not that slow.  Considering the alternatives (Reflection.EmitDynamic Languages and others) and the complexity they can introduce I think I'll leave it as is.  If you can see any serious flaws in this logic let me know.
-Ben

No comments:

Post a Comment