Activator.CreateInstance and beyond

Q: Assume we have 2000 unknown types; (however) we know each type has a constructor with integer as its' only parameter type. How to create objects 10000 times for each type (and make such late-new fast)?

Activator.CreateInstance comes to my fingers first. It is just so convenient to use: calling Activator.CreateInstance(type, new object[] {100}) in a loop. Done... I already heard somebody is yelling "this API is slow" ;) Yeah, each time it need figure out the right ConstructorInfo (by calling GetConstructors to get all ctors, parsing the constructor method signature/comparing, and binding to the most matched one). If you happen to have Visual Studio Team System installed, you can use this code to do a sample profiling and see what is going on there.

Activator.CreateInstance(type, 100);

Reflection Emit can optimize this scenario a bit: we create an in-memory module and a helper class "ClassFactory", then define one helper method for each type with the early-bind call (strong typed new, see OpCodes.Newobj below); the emitted method is equivalent to "public static Type TypeName(int arg1){ return new Type(arg1); }" in C#.

AssemblyBuilder asmBldr = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("InMemory"), AssemblyBuilderAccess.Run);
ModuleBuilder modBldr = asmBldr.DefineDynamicModule("helper");
TypeBuilder typeBldr = modBldr.DefineType("ClassFactory");

MethodBuilder methBldr = typeBldr.DefineMethod(type.Name, MethodAttributes.Public | MethodAttributes.Static, type, new Type[] { typeof(int) });
ILGenerator ilgen = methBldr.GetILGenerator();
ilgen.Emit(OpCodes.Newobj, type.GetConstructor(new Type[] { typeof(int) }));

Type baked = typeBldr.CreateType();

MethodInfo mi = baked.GetMethod(type.Name);

mi.Invoke(null, new object[] { 100 });

The code in italic need be put in a loop to repeat on 2000 types. We cache the MethodInfo (after baked) to a Dictionary<Type, MethodInfo> so that we can quickly retrieve the MethodInfo for massive consequent Invoke calls.

With DynamicMethod in CLR 2.0, we can avoid that lengthy preparation and bake steps (yes, light-weight codegen). For each type, simply create one dynamic method with the early-bind newobj, and cache it for future use.

DynamicMethod dm = new DynamicMethod("MyCtor", type, new Type[] { typeof(int) }, typeof(LateNew).Module);
ILGenerator ilgen = dm.GetILGenerator();
ilgen.Emit(OpCodes.Newobj, type.GetConstructor(new Type[] { typeof(int) }));

dm.Invoke(null, new object[] { 100 });

Do not forget ConstructorInfo.Invoke. This approach caches the RuntimeConstructorInfo of the exact constructor which we can then invoke on directly; on the other hand, previous Emit/DynamicMethod approaches seemly add one more layer late-invocation.

ConstructorInfo ci = t.GetConstructor(new Type[] { typeof(int) });

ci.Invoke(new object[] { 100 });

If you subsribe Joel Pobar's blog , you may already notice "delegate call in whidbey is close to callvirt in term of performance". Instead of caching the baked MethodInfo (or DynamicMethod), we should spend a bit more time on the preparation stage: to create the delegate (function pointer) for those on-the-fly methods and cache them . Assume I already defined such helper delegate type as follows:

 public delegate object CtorInt32Delegate(int arg);

Let's call next approach as Reflection.Emit+Delegate, we can leverage those codes for the Reflection Emit approach above, and add one line of code to create the delegate:

Type baked = typeBldr.CreateType();
MethodInfo mi = baked.GetMethod(type.Name);

CtorInt32Delegate d = (CtorInt32Delegate)Delegate.CreateDelegate(typeof(CtorInt32Delegate), mi);

d(100); // C# syntax shortcut to d.Invoke(100);

We do the similar thing for the DynamicMethod + Delegate approach :

DynamicMethod dm = new DynamicMethod("MyCtor", type, new Type[] { typeof(int) }, typeof(LateNew).Module);
// get ILGenerator, and Emit call squences
CtorInt32Delegate d = (CtorInt32Delegate)dm.CreateDelegate(typeof(CtorInt32Delegate));


The following table lists the result of  these 6 approaches (no doubt there are other good solutions to the original question) when running the test code at my machine: the 2nd column shows the preparation time (code in the first box of each pair); the 3rd column is the object creation time cost (the second box). Among them, the last 2 approaches show the best performance. If you are wondering the early-bind cost for the same task, it is roughly 1 second at my machine.

Approach Preparation Time(sec) Object Creation Time (sec)
Activator.CreateInstance -- 00:09:28.4796646
Reflection.Emit 00:00:01.5781553 00:02:28.7684813
DynamicMethod.Invoke 00:00:00.0781265 00:02:04.3930133
ConstructorInfo.Invoke 00:00:00.0468759 00:00:56.0167005
Reflection.Emit+Delegate 00:00:01.4218750 00:00:04.2968750
DynamicMethod+Delegate 00:00:00.1093771 00:00:05.7031250

What if the constructor is parameterless? Here is the result (side by side with previous scenario, the object creation cost listed):

Approach Parameterless Parameter
Activator.CreateInstance 00:00:54.1729151 00:09:28.4796646
Reflection.Emit 00:02:10.3775032 00:02:28.7684813
DynamicMethod.Invoke 00:01:37.6581250 00:02:04.3930133
ConstructorInfo.Invoke 00:00:44.1883484 00:00:56.0167005
Reflection.Emit+Delegate 00:00:04.3125000 00:00:04.2968750
DynamicMethod+Delegate 00:00:05.5313562 00:00:05.7031250

We noticed:

  • Activator.CreateInstance approach is actually not that bad here: this API has different path to handle parameterless scenarios.
  • No significant differences between these 2 scenarios for other 5 approaches.

Furthermore, if only less than 16 types are frequently used, Activator.CreateInstance is "fast" API:  internally it creates some sort of delegate directly on the type's parameterless constructor (no dynamic generated methods involved), and cache them. However, the cache size is 16, and 'Least Recently Used'-like cache algorithm is applied. The table below shows the time cost for creating objects of 16 or 17 different types sequentially 2000000 times.

# types used  Call Activator.CreateInstance(Type type) Reflection.Emit+Delegate
16 00:00:10.8439582 00:00:01.5156541 + 00:00:04.2344563
17 00:01:27.6110571 00:00:01.5312794 + 00:00:04.4844611