The Code Slinger

December 3, 2007

Dynamic Object Instantiation: Performance (Part II)

Filed under: .NET,C#,MSIL — Pete @ 8:50 pm

After implementing the findings from Part I I got to thinking about how to utilize the same basic concept for creating, caching and calling property delegates.  Properties are basically just wrappers on two separate method calls from the MSIL’s perspective, a getter and a setter.  So much like I was able to speed up object creation from a generic Proxy type class by writing custom IL code to allow the creation and caching of a delegate that effectively called the constructor of the object, I should be able to do the same thing for creating, caching and calling the properties of a particular class in a generic fashion.

As I was creating a demonstration of such, I found that it was quite simple to create the method (get/set) invocation code.  However in doing so I had to hard code the property type (int, string, bool, etc) since the Proxy container class is only concerned with business object types.   To get around this, I created a separate class called PropertyCaller which is concerned only with the type of the class which contains the property and the property type itself. These two pieces of information, along with the information gleaned from the PropertyInfo object (such as the property Name) allow me to identify a delegate call (get or set) uniquely across any class object I pass in.

Some code may help explain this.  Here’s our basic test class, which just has a single property called ID, that happens to be of type int.

        public class TestClassData

        {

            public int ID { get; set; }

        }

Now, if we want to call this property dynamically, the old way (using the PropertyInfo) object would be to use something like:

        private static void CallViaPI(int id)

        {

            //Init

            TestClassData tcd = new TestClassData();

            PropertyInfo pi = typeof(TestClassData).GetProperty(“ID”);

 

            //Call

            pi.SetValue(tcd, id, null);

            int val = (int)pi.GetValue(tcd, null);

        }

This is slow.  On the order of about 400x what the cost would be to call the ID property directly.  So using our newfound knowledge of creating delegates based on DynamicMethod objects, let’s write a couple of helper methods to do just that.   For the sake of brevity, I’m going to post the entire class I created for this purpose here.  I’ll dissect it below.

    public sealed class PropertyCaller<T, K> where T : class

    {

        private static Dictionary<Type, Dictionary<Type, Dictionary<string, GenGetter>>> dGets =

            new Dictionary<Type, Dictionary<Type, Dictionary<string, GenGetter>>>();

        private static Dictionary<Type, Dictionary<Type, Dictionary<string, GenSetter>>> dSets =

            new Dictionary<Type, Dictionary<Type, Dictionary<string, GenSetter>>>();

 

        public delegate void GenSetter(T target, K value);

        public delegate K GenGetter(T target);

 

        private PropertyCaller()

        {

        }

 

        public static GenGetter CreateGetMethod(PropertyInfo pi)

        {

            //Create the locals needed.

            Type classType = typeof(T);

            Type returnType = typeof(K);

            string propertyName = pi.Name;

 

            //Let’s return the cached delegate if we have one.

            if (dGets.ContainsKey(classType))

            {

                if (dGets[classType].ContainsKey(returnType))

                {

                    if (dGets[classType][returnType].ContainsKey(propertyName))

                        return dGets[classType][returnType][propertyName];

                }

            }

 

            //If there is no getter, return nothing

            MethodInfo getMethod = pi.GetGetMethod();

            if (getMethod == null)

                return null;

 

            //Create the dynamic method to wrap the internal get method

            Type[] arguments = new Type[1];

            arguments[0] = typeof(T);

 

            DynamicMethod getter = new DynamicMethod(

                String.Concat(“_Get”, pi.Name, “_”),

                typeof(K),

                new Type[] { typeof(T) },

                pi.DeclaringType);

            ILGenerator gen = getter.GetILGenerator();

            gen.DeclareLocal(typeof(K));

            gen.Emit(OpCodes.Ldarg_0);

            gen.Emit(OpCodes.Castclass, pi.DeclaringType);

            gen.EmitCall(OpCodes.Callvirt, getMethod, null);

            gen.Emit(OpCodes.Ret);

 

            //Create the delegate and return it

            GenGetter genGetter = (GenGetter)getter.CreateDelegate(typeof(GenGetter));

 

            //Cache the delegate for future use.

            Dictionary<Type, Dictionary<string, GenGetter>> tempDict = null;

            Dictionary<string, GenGetter> tempPropDict = null;

            if (!dGets.ContainsKey(classType))

            {

                tempPropDict = new Dictionary<string, GenGetter>();

                tempPropDict.Add(propertyName, genGetter);

                tempDict = new Dictionary<Type, Dictionary<string, GenGetter>>();

                tempDict.Add(returnType, tempPropDict);

                dGets.Add(classType, tempDict);

            }

            else

            {

                if (!dGets[classType].ContainsKey(returnType))

                {

                    tempPropDict = new Dictionary<string, GenGetter>();

                    tempPropDict.Add(propertyName, genGetter);

                    dGets[classType].Add(returnType, tempPropDict);

                }

                else

                {

                    if (!dGets[classType][returnType].ContainsKey(propertyName))

                        dGets[classType][returnType].Add(propertyName, genGetter);

                }

            }

            //Return delegate to the caller.

            return genGetter;

        }

 

        public static GenSetter CreateSetMethod(PropertyInfo pi)

        {

            //Create the locals needed.

            Type classType = typeof(T);

            Type returnType = typeof(K);

            string propertyName = pi.Name;

 

            //Let’s return the cached delegate if we have one.

            if (dSets.ContainsKey(classType))

            {

                if (dSets[classType].ContainsKey(returnType))

                {

                    if (dSets[classType][returnType].ContainsKey(propertyName))

                        return dSets[classType][returnType][propertyName];

                }

            }

 

            //If there is no setter, return nothing

            MethodInfo setMethod = pi.GetSetMethod();

            if (setMethod == null)

                return null;

 

            //Create dynamic method

            Type[] arguments = new Type[2];

            arguments[0] = typeof(T);

            arguments[1] = typeof(K);

 

            DynamicMethod setter = new DynamicMethod(

                String.Concat(“_Set”, pi.Name, “_”),

                typeof(void),

                arguments,

                pi.DeclaringType);

            ILGenerator gen = setter.GetILGenerator();

            gen.Emit(OpCodes.Ldarg_0);

            gen.Emit(OpCodes.Castclass, pi.DeclaringType);

            gen.Emit(OpCodes.Ldarg_1);

 

            if (pi.PropertyType.IsClass)

                gen.Emit(OpCodes.Castclass, pi.PropertyType);

 

            gen.EmitCall(OpCodes.Callvirt, setMethod, null);

            gen.Emit(OpCodes.Ret);

 

            //Create the delegate

            GenSetter genSetter = (GenSetter)setter.CreateDelegate(typeof(GenSetter));

 

            //Cache the delegate for future use.

            Dictionary<Type, Dictionary<string, GenSetter>> tempDict = null;

            Dictionary<string, GenSetter> tempPropDict = null;

            if (!dSets.ContainsKey(classType))

            {

                tempPropDict = new Dictionary<string, GenSetter>();

                tempPropDict.Add(propertyName, genSetter);

                tempDict = new Dictionary<Type, Dictionary<string, GenSetter>>();

                tempDict.Add(returnType, tempPropDict);

                dSets.Add(classType, tempDict);

            }

            else

            {

                if (!dSets[classType].ContainsKey(returnType))

                {

                    tempPropDict = new Dictionary<string, GenSetter>();

                    tempPropDict.Add(propertyName, genSetter);

                    dSets[classType].Add(returnType, tempPropDict);

                }

                else

                {

                    if (!dSets[classType][returnType].ContainsKey(propertyName))

                        dSets[classType][returnType].Add(propertyName, genSetter);

                }

            }

            //Return delegate to the caller.

            return genSetter;

        }

    }

The two main methods are CreateSetMethod and CreateGetMethod.  They basically handle the creation of the proper delegate based on 1) the class type, 2) the property return type and 3) the property name. These 3 pieces of information are critical to creating a delegate which will end up calling the right class’s property.   Walking through the basics of each we see that first we check the internal cache to short circuit if we already have a delegate which matches the information provided.  This internal cache is simply made up of a multi-dimensional dictionary that categorizes the delegates by:

  • Class Type
  • Property return Type
  • Property Name

Next if we don’t have it cached, we need to create the appropriate delegate.  The basics of this consist of 1) creating the method’s parameters, 2) creating a DynamicMethod wrapper, 3) emitting the IL necessary for the method and finally 4) creating the delegate itself.

Finally we need to cache the newly created delegate for future use.  The overhead to create the delegates would make this approach prohibitively slow if we didn’t cache them (on the order of over 60x slower than using the reflective SetValue/GetValue methods!!). 

Here’s a test harness method which would utilize this new dynamic property delegate creation code.  Obviously (as with the reflection method above, in real usage we would split the initizliation code from the actuall usage code, but this should give you a clear view of how simple both are to actually use). 

        private static void CallViaDelegate(int id)

        {

            //Init

            TestClassData tcd = new TestClassData();

            PropertyInfo tpi = typeof(TestClassData).GetProperty(“ID”);

            PropertyCaller<TestClassData, int>.GenGetter getter = PropertyCaller<TestClassData, int>.CreateGetMethod(tpi);

            PropertyCaller<TestClassData, int>.GenSetter setter = PropertyCaller<TestClassData, int>.CreateSetMethod(tpi);

 

            //Call

            setter(tcd, id);

            int val = getter(tcd);

        }

So, how well does it stack up performance you ask?

property_test_full

The delegate method covered is roughly 3X slower than calling the property directly.  Compare that to the old reflection driven approach which is roughly 450X slower and I think you’ll see the power and potential this has!

 

Til next time…keep on slangin!


Example Project Download

Create a free website or blog at WordPress.com.