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

About these ads

7 Comments »

  1. At it again, huh? This is really good stuff!!!

    Comment by ActiveEngine Sensei — December 4, 2007 @ 6:29 am | Reply

  2. Thanks….I’m glad someone other than me finds it interesting (and hopefully useful as well)!

    Comment by thecodeslinger — December 4, 2007 @ 9:11 am | Reply

  3. [...] Dynamic Object Instantiation Part I Dynamic Object Instantiation Part II [...]

    Pingback by New Blood in Deadwood « ActiveEngine — December 5, 2007 @ 6:56 am | Reply

  4. [...] expression.  This expression basically just uses the PropertyCaller<T> class we created previously to get the delegate “getter” for each property on the class.  Calling this [...]

    Pingback by Custom XML Serialization with LINQ « The Code Slinger — December 18, 2007 @ 4:15 pm | Reply

  5. really useful article, that is what i need exactly, … Thanks Soo much :)

    Comment by Hend Hassan — April 20, 2008 @ 10:06 pm | Reply

  6. [...] Part II   Example Project Download Possibly related posts: (automatically generated)Custom XML Serialization with LINQMultiple Generic Type Parameters & Limitations on ConstraintsConstrain a generic Class/Method to a particular typeAsynchronous Programming Part 1 РDelegates [...]

    Pingback by Dynamic Object Instantiation: Performance « The Code Slinger — October 29, 2008 @ 1:01 pm | Reply

  7. Hi there,

    I did implement the code from part I, and it is proving very useful.

    This article also looks pretty interesting. I tried to go over the sample project to understand it better, but it does not compile; there is no reference to the PropDict.

    Is the sample project a more advanced version of the code here. I did not see the multi nested dictionaries that were used here. I was wondering if you got around having them, or maybe the sample project is just more broken out.

    Thanks
    Walter

    Comment by Walter — March 2, 2009 @ 7:44 pm | Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Rubric Theme. Create a free website or blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: