Many before me have discovered DynamicMethod. I read about them some time back but only recently finally got a chance to dig into them and apply them to a section of a system I work on that made heavy use of reflection.
The heavily reflective code in question is part of a generated code block from an ORM system that was then customized by some members of our team that are long since gone. The code builds FK relationships among loaded entities (entity = object representing 1 row from a table). The code could, if looked at from many perspectives, be considered artful. It interrelates these objects using only a small amount of very generic code, using reflection and recursion. However, the reflection significantly affects its performance as shown via use of profiling (specifically Red-Gate Software’s ANTS).
In the past I’ve sped the code up by caching some reflected information – arrays of PropertyInfo objects and MethodInfo objects. This helped, but still didn’t alleviate the overhead of using Invoke to call those Getters, Setters and Methods.
Finally I had a chance to look into DynamicMethods. A DynamicMethod, introduced in .NET 2.0, is a lightweight way to create a method at runtime, encapsulating emitted IL code without having to create a dynamic assembly or type. The JITed code is reclaimed when the DynamicMethod is reclaimed. Further, the DynamicMethod can be hosted anonymously or associated with an existing type.
Cool! So how can DynamicMethods help with reflection?
The code was using reflection to call property getters, setters and other methods, both instance and static. A typical call looks like this:
MethodInfo methodInfo =
typeof(Test).
GetMethod(
"InstanceMethodWithParameters",
BindingFlags.Instance | BindingFlags.Public);
methodInfo.Invoke(m_Test, new object[] {100});
// Property Get
PropertyInfo propertyInfo =
typeof(Test).
GetProperty(
"InstanceProperty",
BindingFlags.Instance | BindingFlags.Public);
propertyInfo.GetValue(m_Test, null);
Of course, as I said earlier, I was already caching the MethodInfo and PropertyInfo objects, which helps save on reflection in getting the “info” objects, but not in the Invoke and GetValue.
So in looking around to avoid reinventing the wheel, I found an article on CodeProject with code to generate DynamicMethods that encapsulate constructors, property gets and sets. The author had gone to the trouble to figure out the necessary IL code to emit to essentially convert a PropertyInfo object into a getter or setter wrapper in a DynamicMethod and the IL code to emit to encapsulate creating an object of a specified type, i.e. calling its constructor.
I looked further and found another set of code that could generate IL needed to call any method, using only a MethodInfo as input. Unfortunately, I lost my source URL for this one and can’t seem to find it again.
I tested all this code out first in a simple console app. It worked great. Looking back at my requirements I saw that I needed to reference static properties. The code from CodeProject didn’t support that. Finally, time to get my hands dirty!
Now, I don’t know IL. I’ve read a bit and I’ve written my share of assembly code (6502, 8088 and Macro-11), so this doesn’t scare me, but it’s been awhile. The easiest thing seemed to be to write code to do what I needed in C#, compile it and then use Reflector to see what the IL is for it. So first, some simple code:
public class Test
{
public static string StaticProperty
{
get { return "StaticPropertyHello"; }
set { string x = value; }
}
}
private static void GetStaticProperty()
{
string test = Test.StaticProperty;
}
Viewing the source for GetStaticProperty in Reflector and switching the output language to IL I saw:
.method private hidebysig static void GetStaticProperty() cil managed
{
.maxstack 1
.locals init ([0] string test)
L_0001: call string StaticProperty.Test::get_StaticProperty()
L_0006: stloc.0
L_0007: ret
}
Looking at the “Get” generator code from the CodeProject article, I saw what it was emitting:
getGenerator.Emit(OpCodes.Ldarg_0);
getGenerator.Emit(OpCodes.Call, getMethodInfo);
BoxIfNeeded(getMethodInfo.ReturnType, getGenerator);
getGenerator.Emit(OpCodes.Ret);
Distinctly different is the first instruction, Ldarg_0. This instruction “Loads the argument at index 0 onto the evaluation stack”. Its purpose is loading the instance (“this”) onto the stack. Obviously for a static property, there is no instance. If you look at the reflected IL code for the sample GetStaticProperty method, there is a NOP (no operation) instruction in place of the Ldarg_0. The NOP instruction actually does nothing and can be eliminated when we emit code for retrieving a static property.
So I was able to easily modify the emitted code to eliminate the Ldarg_0 when the property is static (testing PropertyInfo.IsStatic). Compiled, tested, worked, shipped it.
I’ve tried doing the same for the field get / set generator code, but as of yet have been unsuccessful at getting it right for static fields. Works great with instance fields though.
So how much faster is it? The measurements have been done and published before but I’ll share my measurements here. I included a couple of small timings in the unit tests.
|
Getting an instance property1,000,000 iterations using reflection took 1.0570 s
1,000,000 iterations using DynamicMethod took 0.0150 s
Setting an instance property
1,000,000 iterations using reflection took 1.3280 s
1,000,000 iterations using DynamicMethod took 0.0170 s
Calling an instance method
1,000,000 iterations using reflection took 1.7180 s
1,000,000 iterations using DynamicMethod took 0.3750 s |
After incorporating the techniques in the actual code I was targeting, the measurements I took in my total process, which contained much more processing than just these calls, reduced in execution time by 50%. A huge benefit to a heavily used process.
Many thanks to those who’ve worked so much of this out before me. I’ve taken your code (whoever you are), combined it into a single library, added my static property modifications, written unit tests around it and added some comments and will share the compilation for download here CWI.Library.Reflection.zip.
References: