次の方法で共有


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.Nop);
ilgen.Emit(OpCodes.Ldarg_1);
ilgen.Emit(OpCodes.Newobj, type.GetConstructor(new Type[] { typeof(int) }));
ilgen.Emit(OpCodes.Ret);

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.Ldarg_1);
ilgen.Emit(OpCodes.Newobj, type.GetConstructor(new Type[] { typeof(int) }));
ilgen.Emit(OpCodes.Ret);

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));

d(100);

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

Comments

  • Anonymous
    November 17, 2005
    Aren't you on vacation ? :)

  • Anonymous
    February 15, 2006
    The code seems to be just like psudo code. Can you please explain those codes better? I understand the issue ir comparing, not teaching how it is done. For instance, for dynamic method one, what are the parameters you pass in and how it is actually working???

    Thanks

  • Anonymous
    July 20, 2006
    My result show a different story.  My "Parameter" test have a smiliar result to the "Parameterless" test, ie: DynamicMethod+Delegate approach is 10 times faster than "Activator.CreateInstance" approach.

    I don't quite believe that with parameter, it jump from less than 6 second to almost 9.5 minutes(568 seconds). Something is not quite right. Could you please post your testing source code?  Thanks.

  • Anonymous
    July 20, 2006
    Please find the code here: http://blogs.msdn.com/haibo_luo/articles/494008.aspx.
    At that time, the community server did not support attachment.

  • Anonymous
    July 21, 2006
    Thanks.  What do I do with the perl code?

  • Anonymous
    July 24, 2006
    Ok, you are right.  I wasn't running 2000 types. Thanks, great help.

  • Anonymous
    December 03, 2006
    This is another one of those memory (as in I want to keep some kind of reference to this post) type of

  • Anonymous
    December 07, 2006
    PingBack from http://www.thecolumn.dk/2006/12/08/c/singleton-provider/

  • Anonymous
    January 31, 2007
    If it is the same type you are calling over and over, you can implement IClonable(). Then you could call Activator.CreateInstance once, store the result in a hashtable and use it to get a blank copy of the type. You don't incur the Activator.CreateInstance penalty after the initial run.

  • Anonymous
    July 20, 2007
    我们在项目中经常用哪种方式来反射方法来创建实例,调用指定的方法和属性?它们的性能怎么样?本文收藏了国外上面一些优秀的文章,仅供参考。

  • Anonymous
    December 16, 2007
    PingBack from http://jajahdevblog.com/reshef/?p=26