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.
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.910,000,000 Writes
With Reflection: 5,14 sec
With DynamicMethod: 0.25Factor: 20.7
If you have any suggestions or questions, comment.
No comments:
Post a Comment