2012/08/09

Dynamic methods for accessing fields

A few weeks ago I was facing some performance issues with a DeepCopy class I wrote. The goal of this class is to provide a faster way for deep-copying an object graph rather than serializing and deserializing with BinaryFormatter. The performance analysis of my code showed that getting or setting a field via FieldInfo.GetValue and FieldInfo.SetValue was very slow. So I searched for a possibility to access fields in a faster way. I found some interesting things, but not exactly what i was looking for.

The Idea

I got the basic idea from this page:

Jachman Wordpress - 2000% faster using dynamic method calls/

It describes a way of accessing properties with dynamic methods. The difference between properties and fields is that for accessing a property you have to call it's get- or set-method, when accessing a field the C# compiler will use special IL instructions to get or set the value.

Analyse the IL code (Getter)

The fastest way to access a field is to directly access it:
public class FieldAccessorsExample
{
    public string _field1;
    public int _field2;
}

// access
var instance = new SomeClass();
var value = instance._someField;

To get high performance getters and setters, we should have some methods which will imitate the direct access. I thought of something like this:

public static object Getter(object source)
{
    return ((FieldAccessorsExample)source)._field1;
}
After compiling the code above, we can view the IL code with ILDisassembler from Visual Studio. To understand the IL code, I found the documentation of the OpCodes class very helpful.
.method public hidebysig static object  Getter(object source) cil managed
{
  // Code size       12 (0xc)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  castclass  FieldSetterGetter.FieldAccessorsExample
  IL_0006:  ldfld      string FieldSetterGetter.FieldAccessorsExample::_field1
  IL_000b:  ret
} // end of method FieldAccessorsExample::Getter

For accessing value types the method looks a little bit different.

.method private hidebysig static object  ValueTypeGetter(object source) cil managed
{
  // Code size       17 (0x11)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  castclass  FieldSetterGetter.FieldAccessorsExample
  IL_0006:  ldfld      int32 FieldSetterGetter.FieldAccessorsExample::_field2
  IL_000b:  box        [mscorlib]System.Int32
  IL_0010:  ret
} // end of method FieldAccessorsExample::ValueTypeGetter

With this as the basic template I created the method which will generate our get-function.
public static Func<object, object> GetGetter(FieldInfo fieldInfo)
{
    // create a method without a name, object as result type and one parameter of type object
    // the last parameter is very import for accessing private fields
    var method = new DynamicMethod(string.Empty, typeof(object), new []{ typeof(object) }, fieldInfo.Module, true);
    var il = method.GetILGenerator();

    il.Emit(OpCodes.Ldarg_0); // load the first argument onto the stack (source of type object)
    il.Emit(OpCodes.Castclass, fieldInfo.DeclaringType); // cast the parameter of type object to the type containing the field
    il.Emit(OpCodes.Ldfld, fieldInfo); // store the value of the given field on the stack. The casted version of source is used as instance
    
    if(fieldInfo.FieldType.IsValueType)
        il.Emit(OpCodes.Box, fieldInfo.FieldType); // box the value type, so you will have an object on the stack

    il.Emit(OpCodes.Ret); // return the value on the stack

    return (Func<object, object>)method.CreateDelegate(typeof(Func<object, object>));
}

Analyse the IL code (Setter)

Now we will just do the same for the set-function. First a simple function which could set the value on a given instance.
public void Setter(object target, object value)
{
    ((FieldAccessorsExample)target)._field1 = (string)value;
}

The IL code generated by the compiler is:

.method public hidebysig static void  Setter(object target,
                                             object 'value') cil managed
{
  // Code size       18 (0x12)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  castclass  FieldSetterGetter.FieldAccessorsExample
  IL_0006:  ldarg.1
  IL_0007:  castclass  [mscorlib]System.String
  IL_000c:  stfld      string FieldSetterGetter.FieldAccessorsExample::_field1
  IL_0011:  ret
} // end of method FieldAccessorsExample::Setter
Or the following for value types:
.method public hidebysig static void  ValueTypeSetter(object target,
                                                      object 'value') cil managed
{
  // Code size       18 (0x12)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  castclass  FieldSetterGetter.FieldAccessorsExample
  IL_0006:  ldarg.1
  IL_0007:  unbox.any  [mscorlib]System.Int32
  IL_000c:  stfld      int32 FieldSetterGetter.FieldAccessorsExample::_field2
  IL_0011:  ret
} // end of method FieldAccessorsExample::ValueTypeSetter

From this we can create the function, which will return our setter function:
public static Action<object, object> GetSetter(FieldInfo fieldInfo)
{
    var method = new DynamicMethod(string.Empty, null, new[] { typeof(object), typeof(object) }, fieldInfo.Module, true);
    var il = method.GetILGenerator();

    il.Emit(OpCodes.Ldarg_0); // load the first argument onto the stack (source of type object)
    il.Emit(OpCodes.Castclass, fieldInfo.DeclaringType); // cast the parameter of type object to the type containing the field
    il.Emit(OpCodes.Ldarg_1); // push the second argument onto the stack (this is the value)

    if (fieldInfo.FieldType.IsValueType)
        il.Emit(OpCodes.Unbox_Any, fieldInfo.FieldType); // unbox the value parameter to the value-type
    else
        il.Emit(OpCodes.Castclass, fieldInfo.FieldType); // cast the value on the stack to the field type

    il.Emit(OpCodes.Stfld, fieldInfo); // store the value on stack in the field
    il.Emit(OpCodes.Ret); // return

    return (Action<object, object>)method.CreateDelegate(typeof(Action<object, object>));
}

Usage

If you put the two functions above in a factory class, the usage could look like this:

var getter = FieldAccessorFactory.GetGetter(fieldInfo);
var setter = FieldAccessorFactory.GetSetter(fieldInfo);
 
var oldValue = getter(instance);
setter(instance, "newvalue");
Some helpful informations can also be found by searching with google for "open delegates" in C#.
This page explains the basics.

Performance

10,000,000 Reads

With Reflection: 3.77 sec
With DynamicMethod: 0.17 sec
Factor: 21.9


10,000,000 Writes

With Reflection: 5,14 sec
With DynamicMethod: 0.25
Factor: 20.7

If you have any suggestions or questions, comment.


No comments:

Post a Comment