次の方法で共有


Reflection : Fast Object Creation

Reflection is a powerful api in the .net framework.  Reflection allow us to inspect types at runtime, dynamically invoke methods, create objects, etc.  Using reflection developers can make highly modular applications where functionality can be replaced and defined at runtime. 

One of the drawbacks of reflection is performance.  Using reflection to set a property, invoke a method, create an object, etc is several times slower than normal code.  There are ways to get around these performance issues though.  If you have not already done so I recommend that you read the MSDN Magazine article Reflection : Dodge Common Performance Pitfalls to Craft Speedy Applications.  The article goes over some really good techniques on how to get your late-bound code running as fast as possible.

In most applications developers will use some type of strongly typed "contract" to talk to the late-bound object.  This contract could be an interface, abstract class, or delegate.  The MSDN article goes over this and talks about how to use this technique to mitigate most of the performance issues.  One thing that is not covered is object creation.  There is no way in .Net to get a strongly typed contract for an object constructor.  To create a late bound object at runtime you must call Activator.CreateInstance, ConstructorInfo.Invoke, etc.  Most application are not trying to create millions of late-bound objects every second.  It's nice to know we have the ability though if we need it :)

What if the .net framework had a way to expose a strongly typed contract to a constructor?  Here is how I imagine it would work.  There would be a new method on the ConstructorInfo called CreateDelegate.  This method will take the delegate type and return a delegate that can be used to create the object.  Below is a sample.

            ConstructorInfo constructorInfo = typeof(MyClass).GetConstructor(new Type[]{typeof(int)});
            Func<int,MyClass> del = constructorInfo.CreateDelegate(typeof(Func<int,MyClass>)) as Func<int,MyClass>;
            object j = del(1);

This feature does not exist in the .net framework but we can add it ourselves.  A ConstructorInfo inherits from MethodBase. Can we just bind the constructor directly to a delegate?  I don't think so.  When a new object is created the newobj opcode is used opposed call, calli, and callvirt opcodes.  A constructor does not return the object.  The object or "this" is a hidden argument that is passed in to the constructor and every non static method.  The newobj opcode/new operator creates the object, then calls the constructor to initialize the object, and returns the result to the caller.  One opcode for two separate things per say.  Let try to bind to a delegate to a constructor and see what happens.  There is a internal CreateDelegate method on the delegate class that takes a runtime method handle that we will be using.  The sample code is below.

    class Program
    {

        static void Main(string[] args)
        {

            // Create the object
            MyClass testObj = new MyClass(1);
            // Write the value.
            Console.WriteLine("Class Value {0}", testObj.Value);
            // Get a delegate to the constructor.
            ConstructorInfo constructorInfo = typeof(MyClass).GetConstructor(new Type[]{typeof(int)});
            Action<int> del = constructorInfo.BindDelegate(typeof(Action<int>), testObj) as Action<int>;
            // Call the constructor with a new value
            del(2);
            Console.WriteLine("Class Value {0}", testObj.Value);
            // Call the constructor with a new value
            del(3);
            Console.WriteLine("Class Value {0}", testObj.Value);
            Console.ReadLine();
        }
        public class MyClass
        {
            private readonly int _value;
            public MyClass (int value)
            {
                Console.WriteLine("Constructor Invoke Current Value {0}", _value);
                _value=value;
                Console.WriteLine("Constructor Invoke New Value {0}", _value);
            }
            public int Value
            {
                get
                {
                    return _value;
                }
            }
        }
    }

    public static class ConstructorHelper
    {
        private delegate Delegate CreateDelegateHandler(Type type, object target, RuntimeMethodHandle handle);
        private static CreateDelegateHandler _createDelegate;

        static ConstructorHelper()
        {
            MethodInfo methodInfo =
                typeof(Delegate).GetMethod("CreateDelegate", BindingFlags.Static|BindingFlags.NonPublic, null,
                new Type[] { typeof(Type), typeof(object), typeof(RuntimeMethodHandle) }, null);
            _createDelegate = Delegate.CreateDelegate(typeof(CreateDelegateHandler), methodInfo) as CreateDelegateHandler;
        }
        public static Delegate BindDelegate(this ConstructorInfo constructor,
            Type delegateType, object obj)
        {
            return _createDelegate(delegateType, obj, constructor.MethodHandle);
        }
    }

A couple interesting things happening here.  First off the code works.  That really surprised me.  The constructor is invoked 3 times on the same object.  The next interesting thing is we are able to write to a readonly member variable several times.  This is getting us off track though.  Lets go back to creating a strongly typed contract for a constructor.

Our next method is using the reflection emit to create a new method that will create and return the object.  We can bind a delegate to this new method and we are done :)  We have a strongly typed contract that we can use to invoke our constructor not directly (the new method calls the constructor) but still several times faster than the other techniques.

        public static Delegate CreateDelegate(this ConstructorInfo constructor,
            Type delegateType)
        {
            if (constructor == null)
            {
                throw new ArgumentNullException("constructor");
            }
            if (delegateType == null)
            {
                throw new ArgumentNullException("delegateType");
            }

            // Validate the delegate return type
            MethodInfo delMethod = delegateType.GetMethod("Invoke");
            if (delMethod.ReturnType != constructor.DeclaringType)
            {
                throw new InvalidOperationException("The return type of the delegate must match the constructors delclaring type");
            }

            // Validate the signatures
            ParameterInfo[] delParams = delMethod.GetParameters();
            ParameterInfo[] constructorParam = constructor.GetParameters();
            if (delParams.Length != constructorParam.Length)
            {
                throw new InvalidOperationException("The delegate signature does not match that of the constructor");
            }
            for (int i = 0; i < delParams.Length; i++)
            {
                if (delParams[i].ParameterType != constructorParam[i].ParameterType ||  // Probably other things we should check ??
                    delParams[i].IsOut)
                {
                    throw new InvalidOperationException("The delegate signature does not match that of the constructor");
                }
            }
            // Create the dynamic method
            DynamicMethod method =
                new DynamicMethod(
                    string.Format("{0}__{1}", constructor.DeclaringType.Name, Guid.NewGuid().ToString().Replace("-","")),
                    constructor.DeclaringType,
                    Array.ConvertAll<ParameterInfo, Type>(constructorParam, p => p.ParameterType),
                    true
                    );

            // Create the il
            ILGenerator gen = method.GetILGenerator();
            for (int i = 0; i < constructorParam.Length; i++)
            {
                if (i < 4)
                {
                    switch (i)
                    {
                        case 0:
                            gen.Emit(OpCodes.Ldarg_0);
                            break;
                        case 1:
                            gen.Emit(OpCodes.Ldarg_1);
                            break;
                        case 2:
                            gen.Emit(OpCodes.Ldarg_2);
                            break;
                        case 3:
                            gen.Emit(OpCodes.Ldarg_3);
                            break;
                    }
                }
                else
                {
                    gen.Emit(OpCodes.Ldarg_S, i);  
                }
            }
            gen.Emit(OpCodes.Newobj, constructor);
            gen.Emit(OpCodes.Ret);

            // Return the delegate :)
            return method.CreateDelegate(delegateType);

        }

Below are some results from some performance tests.  I ran it twice to give the runtime a chance to "warm up".  As we can see creating the object using the delegate is much, much faster than Activator.CreateInstance and close to the performance of direct call.

Running 1000000 interations of creation test.
Direct Call                     00:00:00.0643122
Delegate Wrapper                00:00:00.0691446
Activator.CreateInstance        00:00:06.8385526

Attached is some of the sample code.  We might look at building on this in a later post.  Peace until next time :)

Reflection : Dodge Common Performance Pitfalls to Craft Speedy Applications
https://msdn.microsoft.com/en-us/magazine/cc163759.aspx

Share/Save/Bookmark

Program.cs

Comments