|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionReflection is a feature that allows a program to find out type (and metadata) information about objects at run-time. Programs written in languages that support Reflection, like Java and the CLR languages family (C#, Visual Basic .NET etc.) can inspect types, obtain detailed information about class members, dynamically instantiate classes and invoke methods at run-time. The .NET Framework exposes its reflection services through the Joel Pobar in his article Dodge Common Performance Pitfalls to Craft Speedy Applications offers a good overview over .NET Reflection performance, as well as describes a way to obtain fast late-bound invocation, the implementation of which is discussed in this article. Hybrid Late-Bound to Early-Bound Invocation through DynamicMethod DelegatesThe basic idea presented by Joel Polbar is to obtain a method handle through the common Reflection services, and then emit MSIL code to patch up a static call site for the method. This call site will be kept as simple as possible: no type safety / security checks will be performed, assuming that hybrid invocation is done only between fully trusted code entities. Generation of the MSIL call site will be done through a new feature of the .NET Framework 2.0, called Lightweight Code Generation (LGC). Hybrid invocation can be achieved also under .NET Framework 1.1 using the standard The DynamicMethod ClassLightweight Code Generation features are exposed by a single new class inside the public DynamicMethod(
string name,
Type returnType,
Type[] parameterTypes,
Type owner
);
public DynamicMethod(
stringname,
TypereturnType,
Type[] parameterTypes,
Module owner
);
These are two public constructors available for the class. As you can see, a dynamic method is logically associated with a module or with a type ( Code OverviewThe entire implementation resides in a public static DynamicMethodDelegate DynamicMethodDelegateFactory.Create(
MethodInfo method
);
This function takes a public delegate object DynamicMethodDelegate(
object instance,
object[] args
);
The delegate syntax is the same as one of the Using the code is pretty straightforward: MyClass instance = new MyClass();
MethodInfo myMethodInfo;
// [...] myMethodInfo = some method of MyClass
DynamicMethodDelegate myDelegate;
// Generate delegate for myMethodInfo
myDelegate = DynamicMethodDelegateFactory.Create(myMethodInfo);
// Invoke method through delegate.
object[] args = {/* method arguments here */};
object result = myDelegate(instance, args);
Implementation DetailsThe initial part of our method is quite self explaining: /// <summary>
/// Generates a DynamicMethodDelegate delegate from a MethodInfo object.
/// </summary>
public static DynamicMethodDelegate Create(MethodInfo method)
{
ParameterInfo[] parms = method.GetParameters();
int numparams = parms.Length;
Type[] _argTypes = { typeof(object), typeof(object[]) };
// Create dynamic method and obtain its IL generator to
// inject code.
DynamicMethod dynam =
new DynamicMethod(
"",
typeof(object),
_argTypes,
typeof(DynamicMethodDelegateFactory));
ILGenerator il = dynam.GetILGenerator();
/* [...IL GENERATION...] */
return (DynamicMethodDelegate)
dynam.CreateDelegate(typeof(DynamicMethodDelegate));
}
Let's now focus our attention on the IL generation code. Our IL call site will be divided into four sections:
Argument count check// Define a label for succesfull argument count checking.
Label argsOK = il.DefineLabel();
// Check input argument count.
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldlen);
il.Emit(OpCodes.Ldc_I4, numparams);
il.Emit(OpCodes.Beq, argsOK);
// Argument count was wrong, throw TargetParameterCountException.
il.Emit(OpCodes.Newobj,
typeof(TargetParameterCountException).GetConstructor(Type.EmptyTypes));
il.Emit(OpCodes.Throw);
// Mark IL with argsOK label.
il.MarkLabel(argsOK);
In this section we compare the length of the provided input arguments array with the number of parameters accepted by the method. On equality, we proceed to the next section (marked with the Object instance push // If method isn't static push target instance on top
// of stack.
if (!method.IsStatic)
{
// Argument 0 of dynamic method is target instance.
il.Emit(OpCodes.Ldarg_0);
}
Clear and simple. The function call MSIL opcodes ( Argument layout // Lay out args array onto stack.
int i = 0;
while (i < numparams)
{
// Push args array reference onto the stack, followed
// by the current argument index (i). The Ldelem_Ref opcode
// will resolve them to args[i].
// Argument 1 of dynamic method is argument array.
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldc_I4, i);
il.Emit(OpCodes.Ldelem_Ref);
// If parameter [i] is a value type perform an unboxing.
Type parmType = parms[i].ParameterType;
if (parmType.IsValueType)
{
il.Emit(OpCodes.Unbox_Any, parmType);
}
i++;
}
We read each element inside the Method call // Perform actual call.
// If method is not final a callvirt is required
// otherwise a normal call will be emitted.
if (method.IsFinal)
{
il.Emit(OpCodes.Call, method);
}
else
{
il.Emit(OpCodes.Callvirt, method);
}
if (method.ReturnType != typeof(void))
{
// If result is of value type it needs to be boxed
if (method.ReturnType.IsValueType)
{
il.Emit(OpCodes.Box, method.ReturnType);
}
}
else
{
il.Emit(OpCodes.Ldnull);
}
// Emit return opcode.
il.Emit(OpCodes.Ret);
Here we generate the actual method call, process a possible return value, and return from the call site. If our method is final (not virtual nor an interface implementation method) we emit a light Performance ComparisonsAs you can see, the I measured the efficiency of == Testing performance of dynamic method delegates:
== Test call type: static method with boxing on return value
Results for 5 tests on 500000 iterations:
Direct method call: 6ms
Dynamic method delegates: 39ms (Efficiency: 0,15)
MethodInfo.Invoke: 4616ms (Efficiency: 0,001)
== Testing performance of dynamic method delegates:
== Test call type: static method without boxing on return value
Results for 5 tests on 100000 iterations:
Direct method call: 8ms
Dynamic method delegates: 14ms (Efficiency: 0,57)
MethodInfo.Invoke: 894ms (Efficiency: 0,009)
== Testing performance of dynamic method delegates
== Test call type: virtual method without boxing on return value
Results for 5 tests on 10000 iterations:
Direct method call: 10ms
Dynamic method delegates: 13ms (Efficiency: 0,77)
MethodInfo.Invoke: 166ms (Efficiency: 0,06)
ConclusionI've tried to illustrate a simple implementation of one of the ideas exposed inside Joel Polbar's article, along with some observations over the implementation itself, hoping that this work will be at least partly useful in understanding what hybrid invocation is and how it can be achieved. As a conclusive note, I'd like to underline that this technique is not a substitute of standard late-bound invocation through Reflection. History
|
||||||||||||||||||||||