Blog Home  Home Feed your aggregator (RSS 2.0)  
Generic method invocation at runtime without using reflection – DynamicMethods - Manuel Abadia's ASP.NET stuff
 
# Wednesday, 05 July 2006

Being able to dynamically call an arbitrary method on an object or the ability to get/set any arbitrary property by name at run time is very important when it comes to do some generic stuff. In .NET Framework 1.x the only way to do that was using reflection and that means to do it slow. For simple things or if we don’t have a lot of concurrent users we can live with it, but sometimes the performance is important and reflection isn’t an acceptable solution.

Fortunately, the .NET Framework 2.0 introduces a new class that let us to quickly call an arbitrary method without the overhead of a reflection call. How? Well, the idea is to generate the same MSIL as the compiler generates when calling a function but on the fly, and then we obtain a delegate that when called will execute the MSIL we have just created. This is really useful in a lot of scenarios!

I read something about this in Marc’s blog (http://musingmarc.blogspot.com/) but I didn’t have time to look at it in more detail in that moment. Today I read an article at codeproject that talked about this (http://www.codeproject.com/useritems/FastMethodInvoker.asp) so I finally forced myself to spend some time playing with dynamic MSIL code generation and I’m quite impressed with the possibilities.

If you want to play with this you should download an excellent debugger visualizer that will let you show the MSIL generated by a DynamicMethod:

http://blogs.msdn.com/haibo_luo/archive/2005/10/25/484861.aspx

I have made a little helper class (based on the codeproject article) that can be used to replace MethodInfo.Invoke, PropertyInfo.GetValue, PropertyInfo.SetValue and Activator.CreateInstance that are the reflection methods that I use most often. In order to use it in your own projects, remember to cache the delegates obtained because every time you call the CreateDelegate the MSIL generation takes place and that takes some time.

I have also created a simple test to show a comparison between direct calls, reflection calls and dynamic method calls. The results are here:

------ Object Creation ------
direct - object creation: 63ms
dynamic method - object creation: 86ms
reflection - object creation: 391ms
------ Property Get ------
direct - property get: 21ms
dynamic method - property get: 81ms
reflection - property get: 1849ms
------ Property Set ------
direct - property set: 21ms
dynamic method - property set: 67ms
reflection - property set: 2476ms
------ Instance Method Call ------
direct - instance method call: 24ms
dynamic method - instance method call: 154ms
reflection - instance method call: 3266ms
------ Static Method Call ------
direct - static method call: 36ms
dynamic method - static method call: 157ms
reflection - static method call: 3139ms

To summarize:
• For object creation, using reflection is 6.2 times slower than direct object creation. With DynamicMethods is only 1.3 times slower than direct object creation. Using DynamicMethods is about 4.5 times faster than using reflection.
• To get a property using reflection is 90.1 times slower than direct property access. With DynamicMethods is only 3.8 times slower than direct property access. Using DynamicMethods is nearly 23 times faster than using reflection.
• To set a property using reflection is 117.9 times slower than direct property set. With DynamicMethods is only 3.2 times slower than direct property set. Using DynamicMethods is about 37 times faster than using reflection.
• For instance method invocation, using reflection is 136.1 times slower than direct method call. With DynamicMethods is only 6.4 times slower than direct method call. Using DynamicMethods is about 21 times faster than using reflection.
• For static method invocation, using reflection is 87.2 times slower than direct method call. With DynamicMethods is only 4.3 times slower than direct method call. Using DynamicMethods is about 20 times faster than using reflection.

I’m going to modify my ExtendedObjectDataSource to use DynamicMethods in order to gain a 20x speed increase compared to reflection calls.

The code used for the tests and the helper class for the DynamicMethods are shown here and can be downloaded at the end of this post:

using System;

using System.Reflection;

using System.Reflection.Emit;

 

namespace Manu.Utils

{

    /// <summary>Delegate for calling a method that is not known at runtime.</summary>

    /// <param name="target">the object to be called or null if the call is to a static method.</param>

    /// <param name="paramters">the parameters to the method.</param>

    /// <returns>the return value for the method or null if it doesn't return anything.</returns>

    public delegate object FastInvokeHandler(object target, object[] parameters);

 

    /// <summary>Delegate for creating and object at runtime using the default constructor.</summary>

    /// <returns>the newly created object.</returns>

    public delegate object FastCreateInstanceHandler();

 

    /// <summary>Delegate to get an arbitraty property at runtime.</summary>

    /// <param name="target">the object instance whose property will be obtained.</param>

    /// <returns>the property value.</returns>

    public delegate object FastPropertyGetHandler(object target);

 

    /// <summary>Delegate to set an arbitrary property at runtime.</summary>

    /// <param name="target">the object instance whose property will be modified.</param>

    /// <param name="parameter"></param>

    public delegate void FastPropertySetHandler(object target, object parameter);

 

    /// <summary>Class with helper methods for dynamic invocation generating IL on the fly.</summary>

    public static class DynamicCalls

    {

        public static FastInvokeHandler GetMethodInvoker(MethodInfo methodInfo)

        {

            // generates a dynamic method to generate a FastInvokeHandler delegate

            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object), typeof(object[]) }, methodInfo.DeclaringType.Module);

 

            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();

 

            ParameterInfo[] parameters = methodInfo.GetParameters();

 

            Type[] paramTypes = new Type[parameters.Length];

 

            // copies the parameter types to an array

            for (int i = 0; i < paramTypes.Length; i++) {

                if (parameters[i].ParameterType.IsByRef)

                    paramTypes[i] = parameters[i].ParameterType.GetElementType();

                else

                    paramTypes[i] = parameters[i].ParameterType;

            }

 

            LocalBuilder[] locals = new LocalBuilder[paramTypes.Length];

 

            // generates a local variable for each parameter

            for (int i = 0; i < paramTypes.Length; i++) {

                locals[i] = ilGenerator.DeclareLocal(paramTypes[i], true);

            }

 

            // creates code to copy the parameters to the local variables

            for (int i = 0; i < paramTypes.Length; i++) {

                ilGenerator.Emit(OpCodes.Ldarg_1);

                EmitFastInt(ilGenerator, i);

                ilGenerator.Emit(OpCodes.Ldelem_Ref);

                EmitCastToReference(ilGenerator, paramTypes[i]);

                ilGenerator.Emit(OpCodes.Stloc, locals[i]);

            }

 

            if (!methodInfo.IsStatic) {

                // loads the object into the stack

                ilGenerator.Emit(OpCodes.Ldarg_0);

            }

 

            // loads the parameters copied to the local variables into the stack

            for (int i = 0; i < paramTypes.Length; i++) {

                if (parameters[i].ParameterType.IsByRef)

                    ilGenerator.Emit(OpCodes.Ldloca_S, locals[i]);

                else

                    ilGenerator.Emit(OpCodes.Ldloc, locals[i]);

            }

 

            // calls the method

            if (!methodInfo.IsStatic) {

                ilGenerator.EmitCall(OpCodes.Callvirt, methodInfo, null);

            } else {

                ilGenerator.EmitCall(OpCodes.Call, methodInfo, null);

            }

 

            // creates code for handling the return value

            if (methodInfo.ReturnType == typeof(void)) {

                ilGenerator.Emit(OpCodes.Ldnull);

            } else {

                EmitBoxIfNeeded(ilGenerator, methodInfo.ReturnType);

            }

 

            // iterates through the parameters updating the parameters passed by ref

            for (int i = 0; i < paramTypes.Length; i++) {

                if (parameters[i].ParameterType.IsByRef) {

                    ilGenerator.Emit(OpCodes.Ldarg_1);

                    EmitFastInt(ilGenerator, i);

                    ilGenerator.Emit(OpCodes.Ldloc, locals[i]);

                    if (locals[i].LocalType.IsValueType)

                        ilGenerator.Emit(OpCodes.Box, locals[i].LocalType);

                    ilGenerator.Emit(OpCodes.Stelem_Ref);

                }

            }

 

            // returns the value to the caller

            ilGenerator.Emit(OpCodes.Ret);

 

            // converts the DynamicMethod to a FastInvokeHandler delegate to call to the method

            FastInvokeHandler invoker = (FastInvokeHandler)dynamicMethod.CreateDelegate(typeof(FastInvokeHandler));

 

            return invoker;

        }

 

        /// <summary>Gets the instance creator delegate that can be use to create instances of the specified type.</summary>

        /// <param name="type">The type of the objects we want to create.</param>

        /// <returns>A delegate that can be used to create the objects.</returns>

        public static FastCreateInstanceHandler GetInstanceCreator(Type type)

        {

            // generates a dynamic method to generate a FastCreateInstanceHandler delegate

            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, type, new Type[0], typeof(DynamicCalls).Module);

 

            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();

 

            // generates code to create a new object of the specified type using the default constructor

            ilGenerator.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));

 

            // returns the value to the caller

            ilGenerator.Emit(OpCodes.Ret);

 

            // converts the DynamicMethod to a FastCreateInstanceHandler delegate to create the object

            FastCreateInstanceHandler creator = (FastCreateInstanceHandler)dynamicMethod.CreateDelegate(typeof(FastCreateInstanceHandler));

 

            return creator;

        }

 

        public static FastPropertyGetHandler GetPropertyGetter(PropertyInfo propInfo)

        {

            // generates a dynamic method to generate a FastPropertyGetHandler delegate

            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object) }, propInfo.DeclaringType.Module);

 

            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();

 

            // loads the object into the stack

            ilGenerator.Emit(OpCodes.Ldarg_0);

 

            // calls the getter

            ilGenerator.EmitCall(OpCodes.Callvirt, propInfo.GetGetMethod(), null);

 

            // creates code for handling the return value

            EmitBoxIfNeeded(ilGenerator, propInfo.PropertyType);

 

            // returns the value to the caller

            ilGenerator.Emit(OpCodes.Ret);

 

            // converts the DynamicMethod to a FastPropertyGetHandler delegate to get the property

            FastPropertyGetHandler getter = (FastPropertyGetHandler)dynamicMethod.CreateDelegate(typeof(FastPropertyGetHandler));

 

            return getter;

        }

 

        public static FastPropertySetHandler GetPropertySetter(PropertyInfo propInfo)

        {

            // generates a dynamic method to generate a FastPropertySetHandler delegate

            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, null, new Type[] { typeof(object), typeof(object) }, propInfo.DeclaringType.Module);

 

            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();

 

            // loads the object into the stack

            ilGenerator.Emit(OpCodes.Ldarg_0);

 

            // loads the parameter from the stack

            ilGenerator.Emit(OpCodes.Ldarg_1);

 

            // cast to the proper type (unboxing if needed)

            EmitCastToReference(ilGenerator, propInfo.PropertyType);

 

            // calls the setter

            ilGenerator.EmitCall(OpCodes.Callvirt, propInfo.GetSetMethod(), null);

 

            // terminates the call

            ilGenerator.Emit(OpCodes.Ret);

 

            // converts the DynamicMethod to a FastPropertyGetHandler delegate to get the property

            FastPropertySetHandler setter = (FastPropertySetHandler)dynamicMethod.CreateDelegate(typeof(FastPropertySetHandler));

 

            return setter;

        }

 

        /// <summary>Emits the cast to a reference, unboxing if needed.</summary>

        /// <param name="il">The MSIL generator.</param>

        /// <param name="type">The type to cast.</param>

        private static void EmitCastToReference(ILGenerator ilGenerator, System.Type type)

        {

            if (type.IsValueType) {

                ilGenerator.Emit(OpCodes.Unbox_Any, type);

            } else {

                ilGenerator.Emit(OpCodes.Castclass, type);

            }

        }

 

        /// <summary>Boxes a type if needed.</summary>

        /// <param name="ilGenerator">The MSIL generator.</param>

        /// <param name="type">The type.</param>

        private static void EmitBoxIfNeeded(ILGenerator ilGenerator, System.Type type)

        {

            if (type.IsValueType) {

                ilGenerator.Emit(OpCodes.Box, type);

            }

        }

 

        /// <summary>Emits code to save an integer to the evaluation stack.</summary>

        /// <param name="ilGeneartor">The MSIL generator.</param>

        /// <param name="value">The value to push.</param>

        private static void EmitFastInt(ILGenerator ilGenerator, int value)

        {

            // for small integers, emit the proper opcode

            switch (value) {

                case -1:

                    ilGenerator.Emit(OpCodes.Ldc_I4_M1);

                    return;

                case 0:

                    ilGenerator.Emit(OpCodes.Ldc_I4_0);

                    return;

                case 1:

                    ilGenerator.Emit(OpCodes.Ldc_I4_1);

                    return;

                case 2:

                    ilGenerator.Emit(OpCodes.Ldc_I4_2);

                    return;

                case 3:

                    ilGenerator.Emit(OpCodes.Ldc_I4_3);

                    return;

                case 4:

                    ilGenerator.Emit(OpCodes.Ldc_I4_4);

                    return;

                case 5:

                    ilGenerator.Emit(OpCodes.Ldc_I4_5);

                    return;

                case 6:

                    ilGenerator.Emit(OpCodes.Ldc_I4_6);

                    return;

                case 7:

                    ilGenerator.Emit(OpCodes.Ldc_I4_7);

                    return;

                case 8:

                    ilGenerator.Emit(OpCodes.Ldc_I4_8);

                    return;

            }

 

            // for bigger values emit the short or long opcode

            if (value > -129 && value < 128) {

                ilGenerator.Emit(OpCodes.Ldc_I4_S, (SByte)value);

            } else {

                ilGenerator.Emit(OpCodes.Ldc_I4, value);

            }

        }

    }

}

using System;

using System.Reflection;

using System.Reflection.Emit;

using System.Collections.Generic;

using System.Diagnostics;

using Manu.Utils;

 

namespace DynamicMethods

{

    public class Program

    {

        public static void Main(string[] args)

        {

            // object creation

 

            Console.WriteLine("------ Object Creation ------");

 

            EvaluateMethod("direct - object creation", delegate() {

                TestClass p = new TestClass();

            }, 1000000);

 

            FastCreateInstanceHandler creator = DynamicCalls.GetInstanceCreator(typeof(TestClass));

 

            EvaluateMethod("dynamic method - object creation", delegate() {

                TestClass p = (TestClass)creator.Invoke();

            }, 1000000);

 

            EvaluateMethod("reflection - object creation", delegate() {

                TestClass p = (TestClass)Activator.CreateInstance(typeof(TestClass));

            }, 1000000);

 

            // property get

 

            Console.WriteLine("------ Property Get ------");

 

            TestClass prod = new TestClass();

            prod.Num = 123;

 

            EvaluateMethod("direct - property get", delegate() {

                int num = prod.Num;

            }, 1000000);

 

            PropertyInfo propInfo = typeof(TestClass).GetProperty("Num");

            FastPropertyGetHandler getter = DynamicCalls.GetPropertyGetter(propInfo);

 

            EvaluateMethod("dynamic method - property get", delegate() {

                int num = (int)getter(prod);

            }, 1000000);

 

            EvaluateMethod("reflection - property get", delegate() {

                int num = (int)propInfo.GetValue(prod, null);

            }, 1000000);

 

            // property set

 

            Console.WriteLine("------ Property Set ------");

 

            EvaluateMethod("direct - property set", delegate() {

                prod.Num = 33;

            }, 1000000);

 

            FastPropertySetHandler setter = DynamicCalls.GetPropertySetter(propInfo);

 

            EvaluateMethod("dynamic method - property set", delegate() {

                setter(prod, 32);

            }, 1000000);

 

            EvaluateMethod("reflection - property set", delegate() {

                propInfo.SetValue(prod, 31, null);

            }, 1000000);

 

            // instance method call

 

            Console.WriteLine("------ Instance Method Call ------");

 

            EvaluateMethod("direct - instance method call", delegate() {

                int result = prod.Multiply(3, 5);

            }, 1000000);

 

            MethodInfo methodInfo1 = typeof(TestClass).GetMethod("Multiply");

            FastInvokeHandler fastInvoker1 = DynamicCalls.GetMethodInvoker(methodInfo1);

 

            EvaluateMethod("dynamic method - instance method call", delegate() {

                int result = (int)fastInvoker1(prod, new object[] { 3, 5 });

            }, 1000000);

 

            EvaluateMethod("reflection - instance method call", delegate() {

                int result = (int)methodInfo1.Invoke(prod, new object[] { 3, 5 });

            }, 1000000);

 

            // static method call

 

            Console.WriteLine("------ Static Method Call ------");

 

            EvaluateMethod("direct - static method call", delegate() {

                int result = TestClass.StaticMultiply(3, 5);

            }, 1000000);

 

            MethodInfo methodInfo2 = typeof(TestClass).GetMethod("StaticMultiply");

            FastInvokeHandler fastInvoker2 = DynamicCalls.GetMethodInvoker(methodInfo2);

 

            EvaluateMethod("dynamic method - static method call", delegate() {

                int result = (int)fastInvoker2(null, new object[] { 3, 5 });

            }, 1000000);

 

            EvaluateMethod("reflection - static method call", delegate() {

                int result = (int)methodInfo2.Invoke(null, new object[] { 3, 5 });

            }, 1000000);

 

            Console.ReadLine();

        }

 

        delegate void MethodToEvaluate();

 

        // evaluates a method the specified number of times and print performance information

        static void EvaluateMethod(string testName, MethodToEvaluate method, long times)

        {

            Stopwatch watch = new Stopwatch();

            watch.Start();

 

            for (long i = 0; i < times; i++) {

                method();

            }

 

            watch.Stop();

 

            Console.WriteLine(testName + ": " + watch.ElapsedMilliseconds + "ms");

        }

    }

}

 

DynamicMethods.zip (5.01 KB)

Wednesday, 05 July 2006 01:47:29 (Romance Daylight Time, UTC+02:00)  #    Comments [3]   ASP.NET | Microsoft .NET Framework  | 
Copyright © 2018 Manuel Abadia. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.