Sunday 6 May 2012

Performance comparison of object instantiation methods in .NET

In the previous post, I compared performance of various methods of code invocation that are in our arsenal in .NET framework.

Last night I was reviewing ASP.NET Web API Source Code that I noticed this snippet:
private static Func<object> NewTypeInstance(Type type)
{
    return  Expression.Lambda<Func<object>>(Expression.New(type)).Compile();
}

But surely, compiling a lambda expression is really costly (as we have seen in the last post), why shouldn't we use simply do this (if we are suppose to just return a Func):
private static Func<object> NewTypeInstance(Type type)
{
    var localType = type; // create a local copy to prevent adverse effects of closure
 Func<object> func = (() => Activator.CreateInstance(localType)); // curry the localType
    return func;
}

Well, I asked this very question from Henrik F Nielsen, ASP.NET Web API team's architect. And he was very helpful getting back to me quickly that "compiling an expression is the fastest way to create an instance. Activator.CreateInstance is very slow".

OK, I did not know that, but it seems to be a good topic for a blog post! So here we are where I compare these few scenarios:

  1. Direct use of the constructor
  2. Using Activator.CreateInstance
  3. Using a previously bound reflected ConstructorInfo (see previous post for more info)
  4. Compiling a lambda expression every time and running it
  5. Caching a compiled lambda expression and running it (what ASP.NET Web API does)

Test and code

In this one, I could get a bit more imaginative with my code since in the last post I already established overhead/merits of various code invocation methods. For the object to construct, I use a simple class which has a default parameterless constructor. Results will be different using a parameterful constructor but I think parameterless constructor is a more pure case.

So I have created a few Action extension methods to perform the tedious repeated snippets in the last post. Each method runs 1,000,000 times which is not high enough but as we will see (and have seen in the last post), compiling a lambda expression every time is really slow so 10 million would be very high.
public class ConstructorComparison
{
 static void Main()
 {
  const int TotalCount = 1000 * 1000; // 1 million
  Stopwatch stopwatch = new Stopwatch();
  Type type = typeof(ConstructorComparison);
  var constructorInfo = type.GetConstructors()[0];
  var compiled = Expression.Lambda<Func<object>>(Expression.New(type)).Compile();

  Action usingConstructor = 
    () => new ConstructorComparison();
  Action usingActivator = 
    () => Activator.CreateInstance(type);
  Action usingReflection = 
    () => constructorInfo.Invoke(new object[0]);
  Action usingExpressionCompilingEverytime =
   () => Expression.Lambda<Func<object>>(Expression.New(type))
    .Compile();
  Action usingCachedCompiledExpression = 
    () => compiled();
  Action<string> performanceOutput = 
    (message) => Console.WriteLine(message);

  Thread.Sleep(1000);
  Console.WriteLine("Warming up ....");
  Thread.Sleep(1000);

  Console.WriteLine("Constructor");
  usingConstructor
   .Repeat(TotalCount)
   .OutputPerformance(stopwatch, performanceOutput)();

  Console.WriteLine("Activator");
  usingActivator
   .Repeat(TotalCount)
   .OutputPerformance(stopwatch, performanceOutput)();

  Console.WriteLine("Reflection");
  usingReflection
   .Repeat(TotalCount)
   .OutputPerformance(stopwatch, performanceOutput)();

  Console.WriteLine("Compiling expression everytime");
  usingExpressionCompilingEverytime
   .Repeat(TotalCount)
   .OutputPerformance(stopwatch, performanceOutput)();

  Console.WriteLine("Using cached compiled expression");
  usingCachedCompiledExpression
   .Repeat(TotalCount)
   .OutputPerformance(stopwatch, performanceOutput)();

  Console.Read();
 }
}

public static class ActionExtensions
{
 public static Action Wrap(this Action action, Action pre, Action post)
 {
  return () =>
       {
           pre();
           action();
           post();
       };
 }

 public static Action OutputPerformance(this Action action, Stopwatch stopwatch, Action<string> output)
 {
  return action.Wrap(
   () => stopwatch.Start(),
   () => 
    {
     stopwatch.Stop();
     output(stopwatch.Elapsed.ToString());
     stopwatch.Reset();
    }
   );
 }

 public static Action Repeat(this Action action, int times)
 {
  return () =>  Enumerable.Range(1, times).ToList()
   .ForEach(x => action());
 }
}

Results and conclusion

Here is output from the program:

Warming up ....
Constructor
00:00:00.0815479
Activator
00:00:00.1732489
Reflection
00:00:00.4263699
Compiling expression everytime
00:02:11.5762143
Using cached compiled expression
00:00:00.0855387

So as we can see:

  • Using a cached compiled expression is almost as fast as the constructor 
  • Using Activator is x2 slower
  • Using reflection is x5 slower
  • Compiling a lambda expression every time is really slow: in this case + x1000 times slower 
So indeed as Henrik said, having only the type of the class, using a cached compiled expression is the fastest method.

5 comments:

  1. Activator.Createinstance < T >() creates an instantiated object of T. But your compiled lambda returns Func< object >(). Should'nt you call Invoke on that func to get a precise result?

    ReplyDelete
  2. I am. Note the () at the end of all lines such as ".OutputPerformance(stopwatch, performanceOutput)()".

    Generic Activator.Createinstance has no place since (Activator.Createinstance<T>) since if you know the T then you might as well call the constructor.

    ReplyDelete
    Replies
    1. It would make sense if Activator.CreateInstance() would take parameters just as the non-generic version does (if you're inside some generic class/method and the type you want to create does not have a parameterless ctor)… but alas, the gods at Microsoft have not bestowed that upon us :| which is really a shame IMHO. Just as there not being any parameterful 'new' constraint.

      Thanks for your experiment, and also for the idea with the compiled expression, that should allow for the creation of a generic factory class where the output type needs parameters for construction, yet still be quick to instantiate!

      PS: I know, I know, it's been a long time, but this blog post is still the first useful thing that pops up when you google C# instantiation performance comparisons ;)

      Delete
    2. Thank you Johann for your kind and warm comments. It is good to know this post still can benefit some in the websphere :)

      Delete
    3. It definitely can!

      As an added remark: Just profiled the expression based factory against direct instantiation; the class I instantiate is a WCF duplex client which takes an InstanceContext of its client implementation as its constructor argument.
      Since the duplex client class does some initialization the first time it's instantiated, I ran my tests externally, and once with direct instantiation first, factory second, and once the other way around, for a hundred times each (and each of the 100 times with the app restarted via a script, to make sure it does all its initialization every time).

      When running the factory instantiation first, it averaged
      15.32ms for factory instantiation
      00.18ms for direct construction (87.5% vs. factory when running direct AFTER factory)

      When running the direct construction first, the averages were
      00.215ms for factory instantiation
      15.22ms for direct construction (99.3% vs factory when running direct BEFORE factory)


      This is with the compiled lambda being generated and cached once for the factory and type, and not included in the measurements here (because it will be insignificant in comparison with the total runtime, in any case, it averaged well below 0.2ms here)

      The factory looks like follows: https://dotnetfiddle.net/SZRvPT (although I moved the instance context construction out of the factory's construction call for profiling). Unfortunately dotnetfiddle doesn't know System.ServiceModel… :P

      Hope this helps someone, too!

      Delete

Note: only a member of this blog may post a comment.