Activator.CreateInstance<T> vs Compiled Expression. Inverse performance on two different machines

StackOverflow https://stackoverflow.com/questions/12307519

Question

A friend and I were testing using compiled expressions for object creation instead of Activator.CreateInstance<T> and ran into some interesting results. We found that when we ran the same code on each of our machines we saw completely opposite results. He got the expected result, significantly better performance out of the compiled expression, whereas I was surprised to see Activator.CreateInstance<T> out perform by 2x.

Both computers ran compiled in .NET 4.0

Computer 1 has .NET 4.5 installed. Computer 2 does not.

Computer 1 over 100000 objects:

45ms - Type<Test>.New()
19ms - System.Activator.CreateInstance<Test>();

Computer 2 over 100000 objects:

13ms - Type<Test>.New()
86ms - System.Activator.CreateInstance<Test>();

And here is the code:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;

namespace NewNew
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch benchmark = Stopwatch.StartNew();
            for (int i = 0; i < 100000; i++)
            {
                var result = Type<Test>.New();
            }
            benchmark.Stop();
            Console.WriteLine(benchmark.ElapsedMilliseconds + " Type<Test>.New()");

            benchmark = Stopwatch.StartNew();
            for (int i = 0; i < 100000; i++)
            {
                System.Activator.CreateInstance<Test>();
            }
            benchmark.Stop();
            Console.WriteLine(benchmark.ElapsedMilliseconds + " System.Activator.CreateInstance<Test>();");
            Console.Read();
        }


        static T Create<T>(params object[] args)
        {
            var types = args.Select(p => p.GetType()).ToArray();
            var ctor = typeof(T).GetConstructor(types);

            var exnew = Expression.New(ctor);
            var lambda = Expression.Lambda<T>(exnew);
            var compiled = lambda.Compile();
            return compiled;
        }
    }

    public delegate object ObjectActivator(params object[] args);

    public static class TypeExtensions
    {
        public static object New(this Type input, params object[] args)
        {
            if (TypeCache.Cache.ContainsKey(input))
                return TypeCache.Cache[input](args);

            var types = args.Select(p => p.GetType());
            var constructor = input.GetConstructor(types.ToArray());

            var paraminfo = constructor.GetParameters();

            var paramex = Expression.Parameter(typeof(object[]), "args");

            var argex = new Expression[paraminfo.Length];
            for (int i = 0; i < paraminfo.Length; i++)
            {
                var index = Expression.Constant(i);
                var paramType = paraminfo[i].ParameterType;
                var accessor = Expression.ArrayIndex(paramex, index);
                var cast = Expression.Convert(accessor, paramType);
                argex[i] = cast;
            }

            var newex = Expression.New(constructor, argex);
            var lambda = Expression.Lambda(typeof(ObjectActivator), newex, paramex);
            var result = (ObjectActivator)lambda.Compile();
            TypeCache.Cache.Add(input, result);
            return result(args);
        }
    }

    public class TypeCache
    {
        internal static IDictionary<Type, ObjectActivator> Cache;

        static TypeCache()
        {
            Cache = new Dictionary<Type, ObjectActivator>();
        }
    }

    public class Type<T>
    {
        public static T New(params object[] args)
        {
            return (T)typeof(T).New(args);
        }
    }

    public class Test
    {
        public Test()
        {

        }

        public Test(string name)
        {
            Name = name;
        }

        public string Name { get; set; }
    }
}
Was it helpful?

Solution

There are at least two causes for this:

  • The overhead of calling Type<Test>.New() or System.Activator.CreateInstance<Test>() for the first time is relatively large. Because of this, I changed 100000 to 10000000.
  • Build your application in release mode, run it without a debugger.

With those two changes, the two methods take about equally long. On my system, I get between 1100 and 1200 for both methods, sometimes one is a little bit higher, sometimes the other is.

Note that Activator.CreateInstance<T>() can only call the default constructor, while your New() accepts a number of arguments. If you make your New() less powerful, and always use the default constructor there too, it is slightly faster than Activator.CreateInstance<T>() on my system.

Note also that your handling of constructors with parameters does not actually work if two different constructors of the same type should be used, depending on the passed arguments. You choose once which constructor to use for the remainder of the entire program.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top