A Fast Alternative to Activator.CreateObject

I'm working on performance issues and tracked down a place where the code repeatedly instantiating objects via Activator.CreateInstance. The use case doesn't allow for me to change that behavior - the instances must be created. So, I need a faster alternative. Jon Skeet wrote an excellent article titled Making reflection fly and exploring delegates that discusses in depth as well as various alternatives.

Basically, by generating a method I incur the costs of evaluation once, rather than repeatedly like Activator.CreateInstance.

I know the types of parameters I need to use at compile-time, but actual type instantiated is not known until runtime. But I can generate a method at runtime to do the instantiation for me. Better still I can use LINQ expressions to generate that method and not have to emit raw IL.

            public static class FastActivator
            {
                public static Func<T1, TResult> Generate<T1, TResult>()
                {
                    ConstructorInfo constructorInfo = typeof(TResult).GetConstructor(new Type[] { typeof(T1), });

                    #if DEBUG
                        ParameterInfo[] parameters = constructorInfo.GetParameters();
                        ParameterExpression parameterExpression = Expression.Parameter(typeof(T1), parameters[0].Name);
                    #else
                        ParameterExpression parameterExpression = Expression.Parameter(typeof(T1));
                    #endif

                    Expression<Func<T1, TResult>> expression = Expression.Lambda<Func<T1, TResult>>(
                        Expression.New(
                            constructorInfo,
                            parameterExpression),
                        parameterExpression);
                    Func<T1, TResult> functor = expression.Compile();
                    return functor;
                }
            }
          

The code is for a simple case - instanting a type via a constructor that accepts a single argument. But I can easily add additional overrides to handle constructors that accept more parameters.

The preprocessor directives in the middle just make it a little easier to debug. When building in a configuration specifying the DEBUG flag, I will get parameters with names that I can use when looking over an Expression's DebugView property. In a build that does not have the flag, I can skip over that unnecessary information.

So now we have a different problem. I have a strongly typed Func<,> that I can't really use since the resultant type isn't known at compile time. So let's create a weakly typed variant that will do the same work and be in a form that will allow me reference the delegate and cache it.

            public static class WeakFastActivator
            {
                public static Func<object, object> Generate(Type resultantType, Type parameterType)
                {
                    ConstructorInfo constructorInfo = resultantType.GetConstructor(new Type[] { parameterType, });

                    #if DEBUG
                        ParameterInfo[] parameters = constructorInfo.GetParameters();
                        ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "boxed_" + parameters[0].Name);
                    #else
                        ParameterExpression parameterExpression = Expression.Parameter(typeof(object));
                    #endif

                    Expression<Func<object, object>> expression = Expression.Lambda<Func<object, object>>(
                        Expression.Block(
                            Expression.IfThen(
                                Expression.Not(Expression.TypeIs(parameterExpression, parameterType)),
                                Expression.Throw(Expression.Constant(new ArgumentException("Parameter type mismatch.", parameterExpression.Name)))),
                            Expression.Convert(
                                Expression.New(
                                    constructorInfo,
                                    Expression.Convert(parameterExpression, parameterType)),
                                resultType)),
                        parameterExpression);
                    Func<object, object> functor = expression.Compile();
                    return functor;
                }
            }
          

Now I have a method that I can use. I can store it away in a private member and invoke it repeatly to instantiate the objects I need. The weakly-typed variant includes some parameter checking as well so we have some better information in case something goes wrong.

            public static class Program
            {
                // A contrived example of how to use the WeakFastActivator.
                public static void Main()
                {
                    Type runtimeKnownType = ...; // Some type whose constructor accepts an integer.

                    Func<object, object> weakFastActivator = 
                        WeakFastActivator.Generate(runtimeKnownType, typeof(int));

                    // Now the fast activator can be repeatedly used.
                    for(int x = 0; x < int.MaxValue; x++)
                    {
                        // This will throw an ArgumentException if we do not supply an int.
                        object instance = weakFastActivator(x);
                        // Do something with the instance here.
                    }
                }
            }
          

There are three cases we can directly test - using the Activator, using a FastActivator delegate, and invoking the constructor directly.

I have this mock object for testing:

            public sealed class Mock
            {
                private readonly int _parameter;
                public Mock(int parameter)
                {
                    this._parameter = parameter;
                }
                public int Parameter
                {
                    get { return this._parameter; }
                }
            }
          

And three test cases to evaluate:

            private static void Case1()
            {
                Type mockType = typeof(Mock);
                for (int x = 0; x < ITERATION_COUNT; x++)
                {
                    Mock mock = (Mock)Activator.CreateInstance(mockType, new object[] { x, });
                    Debug.Assert(mock.Parameter == x, "Parameter does not match the expected value.");
                }
            }

            private static void Case2()
            {
                Func<int, Mock> fastActivator = FastActivator.GenerateFastActivator<int, Mock>();
                for (int x = 0; x < ITERATION_COUNT; x++)
                {
                    Mock mock = fastActivator(x);
                    Debug.Assert(mock.Parameter == x, "Parameter does not match the expected value.");
                }
            }

            private static void Case3()
            {
                ConstructorInfo constructor = typeof(Mock).GetConstructor(new Type[] { typeof(int), });
                for (int x = 0; x < ITERATION_COUNT; x++)
                {
                    Mock mock = (Mock)constructor.Invoke(new object[] { x, });
                    Debug.Assert(mock.Parameter == x, "Parameter does not match the expected value.");
                }
            }
          

And the results of the test - these are from a Release configuration running without a debugger attached where ITERATION_COUNT = 1000. The tests have a single sample execution and then run the 1000 iterations that are timed.

Case1: 24226.428ms ticks/iteration (22459ms-29665ms; σ=688.468ms)
  Bucket # 1 (22459.000ms-23179.700ms):  17
  Bucket # 2 (23179.700ms-23900.400ms): 389
  Bucket # 3 (23900.400ms-24621.100ms): 300
  Bucket # 4 (24621.100ms-25341.800ms): 238
  Bucket # 5 (25341.800ms-26062.500ms):  45
  Bucket # 6 (26062.500ms-26783.200ms):   9
  Bucket # 7 (26783.200ms-27503.900ms):   1
  Bucket # 8 (27503.900ms-28224.600ms):   0
  Bucket # 9 (28224.600ms-28945.300ms):   0
  Bucket #10 (28945.300ms-29666.000ms):   1
Case2: 1673.090ms ticks/iteration (1305ms-9792ms; σ=585.248ms)
  Bucket # 1 (1305.000ms-2153.800ms): 964
  Bucket # 2 (2153.800ms-3002.600ms):  16
  Bucket # 3 (3002.600ms-3851.400ms):   0
  Bucket # 4 (3851.400ms-4700.200ms):  10
  Bucket # 5 (4700.200ms-5549.000ms):   4
  Bucket # 6 (5549.000ms-6397.800ms):   2
  Bucket # 7 (6397.800ms-7246.600ms):   2
  Bucket # 8 (7246.600ms-8095.400ms):   1
  Bucket # 9 (8095.400ms-8944.200ms):   0
  Bucket #10 (8944.200ms-9793.000ms):   1
Case3: 11153.252ms ticks/iteration (10115ms-14759ms; σ=565.624ms)
  Bucket # 1 (10115.000ms-10579.500ms):  26
  Bucket # 2 (10579.500ms-11044.000ms): 545
  Bucket # 3 (11044.000ms-11508.500ms): 263
  Bucket # 4 (11508.500ms-11973.000ms):  74
  Bucket # 5 (11973.000ms-12437.500ms):  47
  Bucket # 6 (12437.500ms-12902.000ms):  29
  Bucket # 7 (12902.000ms-13366.500ms):   8
  Bucket # 8 (13366.500ms-13831.000ms):   3
  Bucket # 9 (13831.000ms-14295.500ms):   1
  Bucket #10 (14295.500ms-14760.000ms):   4
          

We can see that overall, Case2 runs the fastest - having the lowest per-execution time at 1673.090ms, as well as is consistently fast with 96.4% of the executions being under 2153.800ms which clearly beats the other two approaches.

It's a bit difficult to read through, and really does seem a bit too clever for my own good, but the performance benefits are too good to be ignored.

As for solving the original performance problem, the generated method can be cached and repeatedly reused, which allows for a significant performance increase. There is a one-time cost upon the first invocation when a dynamic assembly is created and loaded, but it's well worth the cost.



Tags

  • .NET

Revisions

  • 9/23/2012 - Added performance test cases to prove results.
  • 9/15/2012 - Added parameter type checking to the weakly-typed activator delegate.
  • 8/19/2012 - Article published.