Using CIL to set/get values from fields and properties

Background:

I'm currently working on a freelance project to optimize (yay, optimization again!) a .NET Entity Persister library of a Software Development firm here in Makati City -- much like a custom built NHibernate, but heavily dependent on reflection to get/set field values (their library has no support for Properties). They hired me because they kind of complaining that their system is so slow, it is taking about several seconds (to minutes) just to retrieve some thousand records -- considering the internals of their library is built on top of DataReader (Imagine, looping DataReader to fill a collection of Entities). According to their big boss, using thrid party O/R solution is not an option, they have to put a fix to their current library.

Looking at the problem:

Assuming we have an entity class SampleEntity:

  public class SampleEntity {
    public string stringField = "Sample Value";
    public string StringProperty { get; set; }
   }

and here's how they are setting the field:

  /// <summary>

  /// Dynamic field setter

  /// </summary>

  public class FieldSetter {

    /// <summary>

    /// Set field dynamically

    /// </summary>

    public static void SetField(object instance,
                               string fieldName, object value) {

      var t = instance.GetType();

      var fieldInfo = t.GetField(fieldName,
                                 BindingFlags.Instance | BindingFlags.Public);

      if (fieldInfo == null)

        throw new ArgumentException("There is no publicly accessible " +

                                    fieldName +
                                    " field found in " + t.FullName + ".");

      fieldInfo.SetValue(instance, value);

    }

  }

the library is actually doing this in loop to setup an entire entity instance, and they repeatedly doing it until the DataReader says stop:

  /// <summary>

  /// Get all entities from underlying data store.

  /// </summary>

  public List<T> SelectAll() {

    Open();

    var selectAll = new SqlCommand(BuildSelectStatement<T>(), Connection);

    var reader = selectAll.ExecuteReader();

    var list = new List<T>();

    if (reader != null)

      while (reader.Read()) {

        var entity = default(T);

        for (var i = 0; i < reader.FieldCount; i++) {

          var fieldName = reader.GetName(i);

          var data = reader.GetData(i);

          FieldSetter.SetField(entity, fieldName, data);

        }

      }

    Close();

    return list;

  }

the only problem I've notice that may be the cause of slow execution is the use of reflection.

Doing it the hard way, and maybe the faster way:

If you play long enough with reflection, you will get the experience that it is a slow because of internal loop overheads (search for matching name and binding attributes).

The approach I thought to workaround the problem is to emit CIL codes directly and call them as dynamic method, something that look like:

   public void SetField(SampleEntity instance, string value) {
     instance.stringField = value;
   }

In CIL, this is how it is coded:

  .method public hidebysig instance void SetField(
          class SampleEntity instance, string 'value') cil managed

  {

    .maxstack 8

    SEG_0001: 0x0003  ldarg.0

    SEG_0002: 0x0004  ldarg.1

    SEG_0003: 0x007   stfld string SampleEntity::stringField

    SEG_0008: 0x002   ret

  }

The lines I highlighted in red are the lines I'm interrested in making dynamic, so let's work on a DynamicMethod for doing just that:

  // We still need to get the field information from reflection

  var fieldInfo = t.GetField(fieldName,
                             BindingFlags.Instance | BindingFlags.Public);

  if (fieldInfo == null)

    throw new ArgumentException("There is no publicly accessible " +
                                fieldName +
                                " field found in " + t.FullName + ".");

 

We only need to get the field information once, during the setup of our DynamicMethod:

  var setter = new DynamicMethod("__set_field_" + fieldName,
                                 null,
                                 new[] { t, fieldInfo.FieldType },
                                 typeof(FieldSetter));

  var setterIL = setter.GetILGenerator();


This is because we need the FieldInfo as a parameter to stfld:

  // ldarg.0 - load argument 1 to eval.stack.0
  setterIL.Emit(OpCodes.Ldarg_0);

   
// ldarg.1 - load argument 2 to eval.stack.1

  setterIL.Emit(OpCodes.Ldarg_1);      

  // stfld f - set the field f of instance stack.0 
  //           with the value from stack.1  

  setterIL.Emit(OpCodes.Stfld, fieldInfo); 
         
  // ret     - exit the method                                
  setterIL.Emit(OpCodes.Ret);               


If you take a closer look at the emitted OpCodes, you'll see that it is simply a replica of CIL above, but with a little twist --  the fieldInfo we've taken from given fieldName.

 

That means we are almost done, we just have to call the DynamicMethod we've created for assigning value to field like this:

 

  setter.Invoke(null,
         new object[] { t,
                        "Kamusta mundo, ok ka lang ba?" });

Saving it for succeeding calls:

There's not much improvement at all if we are going to create DynamicMethod repeatedly, in fact that's even slower than using reflection. We have to save the created DynamicMethods for succeeding calls.


Saving it to Dictionary and then retrieving it every time we have a need for it, will boost the performance real fast (because we only have to create DynamicMethod once for every field we want to set value).

 

Summing it all together, with addition of controlling the creation of setter DynamicMethods, we will be able to write a new FieldSetter class:

  public class FieldSetter {

    public static DynamicMethod GetFieldSetterMethod(object instance, string fieldName) {

      var t = instance.GetType();

      var key = (t.FullName + "_" + fieldName).Replace(".", "_");

      if (!fieldSetter.ContainsKey(key)) {

        lock (fsync) {

          // We still need to get the field information from reflection

          var fieldInfo = t.GetField(fieldName,
                          BindingFlags.Instance | BindingFlags.Public);

          if (fieldInfo == null)

            throw new ArgumentException("There is no publicly accessible " +
                                        fieldName +
                                        " field found in " + t.FullName + ".");

          var setter = new DynamicMethod("__set_field_" + key, null,
                                         new[] { t, fieldInfo.FieldType },
                                         typeof(FieldSetter));

          var setterIL = setter.GetILGenerator();

 

          // ldarg.0 - load argument 1 to eval.stack.0

          setterIL.Emit(OpCodes.Ldarg_0);          

         

          // ldarg.1 - load argument 2 to eval.stack.1
          setterIL.Emit(OpCodes.Ldarg_1);          

         
          // stfld f - set the field f of instance stack.0
          //           with the value from stack.1

          setterIL.Emit(OpCodes.Stfld, fieldInfo); 

                      
          // ret     - exit the method                             

          setterIL.Emit(OpCodes.Ret);              

 

          fieldSetter.Add(key, setter);

        }

      }

      return fieldSetter[key];

    }

 

    /// <summary>

    /// Set the field value of given instance, using the name of the field.

    /// </summary>

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

    /// <param name="fieldName">The name of the field.</param>

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

    public static object SetFieldValue(object instance,
                                       string fieldName,
                                       object value) {

      return GetFieldSetterMethod(instance, fieldName)
             .Invoke(null, new[] { instance });

    }

 

    private static readonly Dictionary<string, DynamicMethod> fieldSetter =
                        new Dictionary<string, DynamicMethod>();

    private static readonly object fsync = new object();

  }

That's about it; I hope you guys find it useful for your own purpose.

Did it worked?

After I've delivered the code to them, they immediately tested it.., and tested it.., and tested it more. Long story short, I guess I made them happy because I've got an extra from the paycheck, and the another deal to support property on their custom EntityPerstister.

I'll be working more with their library, specifically the adding of support to Property and Method with one parameter. That's not much of a work left, and I think I almost got everything (except for the method with one parameter, but it’s practically the same as Property since properties are methods too!).


More codes:

Here, take a look at the classes I've put-up for setting/getting field and property (if you don't know how I’ve implemented it, you can re-read this post, or read more about CIL and DynamicMethod):

// FieldSetter.cs

 

  public class FieldSetter {

    private static DynamicMethod GetFieldSetterMethod(
                                 object instance, string fieldName) {

      var t = instance.GetType();

      var key = (t.FullName + "_" + fieldName).Replace(".", "_");

      if (!fieldSetter.ContainsKey(key)) {

        lock (fsync) {

          // We still need to get the field information from reflection

          var fieldInfo = t.GetField(fieldName,
                                     BindingFlags.Instance |
                                     BindingFlags.Public);

          if (fieldInfo == null)

            throw new ArgumentException("There is no publicly " +                  
                                       
"accessible " +
                                        fieldName +
                                        " field found in " +
                                        t.FullName + ".");

          var setter = new DynamicMethod("__set_field_" + key,
                       null, new[] { t, fieldInfo.FieldType },
                       typeof(FieldSetter));

          var setterIL = setter.GetILGenerator();

 

          // ldarg.0 - load argument 1 to eval.stack.0

          setterIL.Emit(OpCodes.Ldarg_0);          

 

          // ldarg.1 - load argument 2 to eval.stack.1
          setterIL.Emit(OpCodes.Ldarg_1);          

         

          // stfld f - set the field f of instance stack.0
          //           with the value from stack.1

          setterIL.Emit(OpCodes.Stfld, fieldInfo); 

 

          // ret     - exit the method                               

          setterIL.Emit(OpCodes.Ret);              

 

          fieldSetter.Add(key, setter);

        }

      }

      return fieldSetter[key];

    }

 

    public static void SetFieldValue(
                 object instance, string fieldName, object value) {

      GetFieldSetterMethod(instance, fieldName)
                .Invoke(null, new[] { instance, value });

    }

 

    private static readonly Dictionary<string, DynamicMethod>
            fieldSetter = new Dictionary<string, DynamicMethod>();

    private static readonly object fsync = new object();

  }

 

// FieldGetter.cs

 

  public class FieldGetter {

    private static DynamicMethod GetFieldGetterMethod(
                            object instance, string fieldName) {

      var t = instance.GetType();

      var key = (t.FullName + "_" + fieldName).Replace(".", "_");

      if (!fieldGetters.ContainsKey(key)) {

        lock (fsync) {

          var fieldInfo = t.GetField(fieldName,
                            BindingFlags.Instance |
                            BindingFlags.Public);

          if (fieldInfo == null)

            throw new ArgumentException("There is no publicly " + 
                                        "accessible "
+ fieldName +
                                        " field found in " +
                                        t.FullName + ".");

          var getter = new DynamicMethod("__get_field_" + key,
                             fieldInfo.FieldType, new[] { t },
                             typeof(FieldGetter));

          var getterIL = getter.GetILGenerator();

 

          // ldarg.0  - load argument 0 to eval.stack.0

          getterIL.Emit(OpCodes.Ldarg_0);   

 

          // ldfld f  - get the value of field from
          // instance in eval.stack
 and store it
          // on top of eval.stack
                      

          getterIL.Emit(OpCodes.Ldfld, fieldInfo); 

          // ret - return what's on top of eval.stack           
          getterIL.Emit(OpCodes.Ret);              

 

          fieldGetters.Add(key, getter);

        }

      }

      return fieldGetters[key];

    }

 

    public static object GetFieldValue(
                object instance, string fieldName) {

      return GetFieldGetterMethod(instance, fieldName)
                .Invoke(null, new[] { instance });

    }

 

    private static readonly Dictionary<string, DynamicMethod>
             fieldGetters = new Dictionary<string, DynamicMethod>();

    private static readonly object fsync = new object();

  }

 

// PropertySetter.cs

 

  public class PropertySetter {

    private static DynamicMethod GetPropertySetterMethod(
                            object instance, string propertyName) {

      var t = instance.GetType();

      var key = (t.FullName + "_" + propertyName).Replace(".", "_");

      if (!propertySetter.ContainsKey(key)) {

        lock (fsync) {

          var propertyInfo = t.GetProperty(propertyName,
                                           BindingFlags.Instance |
                                           BindingFlags.Public);

          if (propertyInfo == null)

            throw new ArgumentException("There is no publicly " +
                                        "accessible " + propertyName +
                                        " property found in " +
                                        t.FullName + ".");

          if (!propertyInfo.CanWrite)

            throw new ArgumentException("The property " +
                                        propertyName +
                               " has no publicly accessible setter.");

          var setter = new DynamicMethod("__set_property_" + key, null,
                           new[] { t, propertyInfo.PropertyType },
                           typeof(PropertySetter));

          var setterIL = setter.GetILGenerator();

 

          // ldarg.0    - load argument 1 to eval.stack.0

          setterIL.Emit(OpCodes.Ldarg_0); 

 

          // ldarg.1    - load argument 2 to eval.stack.1
         
setterIL.Emit(OpCodes.Ldarg_1); 

          // callvirt p - call set_* method to set the
          // value from eval.stack.1
                             

          setterIL.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());

          // ret        - exit the method

          setterIL.Emit(OpCodes.Ret);                                  

 

          propertySetter.Add(key, setter);

        }

      }

      return propertySetter[key];

    }

 

    public static void SetPropertyValue(
                 object instance, string propertyName, object value) {

      GetPropertySetterMethod(instance, propertyName)
                .Invoke(null, new[] { instance, value });

    }

 

    private static readonly Dictionary<string, DynamicMethod>
             propertySetter = new Dictionary<string, DynamicMethod>();

    private static readonly object fsync = new object();

  }

 

 

// PropertyGetter.cs

 

  public class PropertyGetter {

    private static DynamicMethod GetPropertyGetterMethod(
                               object instance, string propertyName) {

      var t = instance.GetType();

      var key = (t.FullName + "_" + propertyName).Replace(".", "_");

      if (!propertyGetters.ContainsKey(key)) {

        lock (fsync) {

          var propertyInfo = t.GetProperty(propertyName,
                                           BindingFlags.Instance |
                                           BindingFlags.Public);

          if (propertyInfo == null)

            throw new ArgumentException("There is no publicly " +
                                        "accessible "
+ propertyName +
                                        " property found in " +
                                        t.FullName + ".");

          if (!propertyInfo.CanRead)

            throw new ArgumentException("The property " + propertyName
                            + " has no publicly accessible getter.");

 

          var getter = new DynamicMethod("__get_property_" + key,
                            propertyInfo.PropertyType, new[] { t },
                            typeof(PropertyGetter));

          var getterIL = getter.GetILGenerator();

 

          // ldarg.0 - load argument 0 to eval.stack.0

          getterIL.Emit(OpCodes.Ldarg_0);    

 

          // callvirt m - a call to get_* method to get
          // the property value
                          

          getterIL.Emit(OpCodes.Callvirt, propertyInfo.GetGetMethod());

 

          // ret - return the result of get_* call

          getterIL.Emit(OpCodes.Ret);                                  

 

          propertyGetters.Add(key, getter);

        }

      }

      return propertyGetters[key];

    }

 

    public static object SetPropertyValue(
                 object instance, string propertyName) {

      return GetPropertyGetterMethod(instance, propertyName)
                .Invoke(null, new[] { instance });

    }

 

    private static readonly Dictionary<string, DynamicMethod>
           propertyGetters = new Dictionary<string, DynamicMethod>();

    private static readonly object fsync = new object();

  }

 


I hope this still fall under the category of assembly language Big Smile
(even though CIL is not exactly assembly language)

See you again next time.

 

 

 

Published 02-13-2009 1:58 PM by cvega
Filed under:

Comments

# re: Using CIL to set/get values from fields and properties

Saturday, February 14, 2009 9:01 AM by jakelite

great article! one question though. what is the performance characteristics of calling dynamicMethod.Invoke as compared to using dynamicMethod.CreateDelegate and calling the delegate?

perhaps something for a follow up blog post?

cheers!

# re: Using CIL to set/get values from fields and properties

Sunday, February 15, 2009 7:11 AM by cvega

@jakelite,

DynamicMethod.Invoke should be faster (although I didn't tested it yet).

CreateDelegate method will only return a invokable delegate with DynamicMethod signature in it. When you call the delegate, it will then resolve the DynamicMethod back again to call DynamicMethod.Invoke.

-c

# re: Using CIL to set/get values from fields and properties

Sunday, February 15, 2009 5:23 PM by cruizer

as always, great post here chris! :)

i wonder though why they insisted on going with their own O/RM instead of leveraging an existing one like NHibernate. NHibernate probably does what your code does whenever it starts up, since there's a momentary delay as it reads the .hbm.xml mappings, right?

oh well...maybe they're afraid of open source. personally i'm more afraid of the NIH syndrome that's pervasive in the Microsoft community.

# re: Using CIL to set/get values from fields and properties

Sunday, February 15, 2009 7:12 PM by cvega

@cruizer,

I'm not exactly sure how NHibernate does it.

However, given the fact the NHibernate is dependent in Castle.DynamicProxy, I think they are making a proxy-class of an entity class (that is also why Entity's properties should be virtual for this to work) and then doing a DynamicMethod for creating an instance of that proxy through constructor:

public class MyEntity {

  public virtual string Name { get; set; }

}

// NHibernate runtime proxy:

public class MyEntityNHProxy: MyEntity {

 public MyEntityNHProxy(string name) {

   Name = name; // set value here...

 }

 // members are inherited. I'm not sure why

 // they need members to be "virtual".

}

-chris

# MethodInfo.Invoke vs MethodCall Performance on DynamicMethods

Monday, February 16, 2009 10:26 AM by jakelite

I posted a question on cvega ’s article on using CIL getters and setters to speed up the execution