If you find yourself doing anything with dynamically loaded types (e.g., an extension or plugin model), then you need to carefully measure your performance when interacting with those types. Ideally, you can interact with those types via a common interface and avoid most of the issues with dynamically loaded code. This approach is described in Chapter 6 when discussing reflection. If that approach is not possible, use this section to get around the performance problems of invoking dynamically loaded code.
The .NET Framework supports dynamic type allocation with the Activator.CreateInstance method, and dynamic method invocation with MethodInfo.Invoke. Here is an example using these methods:
Assembly assembly = Assembly.Load("Extension.dll"); Type type = assembly.GetType("DynamicLoadExtension.Extension"); object instance = Activator.CreateInstance(type); MethodInfo methodInfo = type.GetMethod("DoWork"); bool result = (bool)methodInfo.Invoke(instance, new object[] { argument });
If you do this only occasionally, then it is not a big deal, but if you need to allocate a lot of dynamically loaded objects or invoke many dynamic function calls, these functions could become a severe bottleneck. Not only does Activator.CreateInstance use significant CPU, but it can cause unnecessary allocations, which put extra pressure on the garbage collector.
There is also potential boxing that will occur if you use value types in either the function’s parameters or return value (as the example above does).
If possible, try to hide these invocations behind an interface known both to the extension and the execution program. If that does not work, code generation may be an appropriate option.
Thankfully, generating code to accomplish the same thing is quite easy. To figure out what code to generate, use a template as an example to generate the IL for you to mimic. For an example, see the DynamicLoadExtension and DynamicLoadExecutor sample projects. DynamicLoadExecutor loads the extension dynamically and then executes the DoWork method. The DynamicLoadExecutor project ensures that DynamicLoadExtension.dll is in the right place with a post-build step and a solution build dependency configuration rather than project-level dependencies to ensure that code is indeed dynamically loaded and executed.
Let’s start with creating a new extension object. To create a template, first understand what you need to accomplish. You need a method with no parameters that returns an instance of the type we need. Your program will not know about the Extension type, so it will just return it as an object. That method looks like this:
object CreateNewExtensionTemplate() { return new DynamicLoadExtension.Extension(); }
Take a peek at the IL and it will look like this:
IL_0000: newobj instance void [DynamicLoadExtension]DynamicLoadExtension.Extension::.ctor() IL_0005: ret
Armed with that knowledge, you can create a System.Reflection.Emit.DynamicMethod, programmatically add some IL instructions to it, and assign it to a delegate which you can then reuse to generate new Extension objects at will.
private static T GenerateNewObjDelegate<T>(Type type) where T:class { // Create a new, parameterless (specified // by Type.EmptyTypes) dynamic method. var dynamicMethod = new DynamicMethod("Ctor_" + type.FullName, type, Type.EmptyTypes, true); var ilGenerator = dynamicMethod.GetILGenerator(); // Look up the constructor info for the type we want to create var ctorInfo = type.GetConstructor(Type.EmptyTypes); if (ctorInfo != null) { ilGenerator.Emit(OpCodes.Newobj, ctorInfo); ilGenerator.Emit(OpCodes.Ret); object del = dynamicMethod.CreateDelegate(typeof(T)); return (T)del; } return null; }
You will notice that the emitted IL corresponds exactly to our template method.
To use this, you need to load the extension assembly, retrieve the appropriate type, and pass it the generator function.
Type type = assembly.GetType("DynamicLoadExtension.Extension"); Func<object> creationDel = GenerateNewObjDelegate<Func<object>>(type); object extensionObj = creationDel();
Once the delegate is constructed you can cache it for reuse (perhaps keyed by the Type object, or whatever scheme is appropriate for your application).
You can use the exact same trick to generate the call to the DoWork method. It is only a little more complicated due to a cast and the method arguments. IL is a stack-based language so arguments to functions must be pushed on to the stack in the correct order before a function call. The first argument for an instance method call must be the method’s hidden this parameter that the object is operating on. Note that just because IL uses a stack exclusively, it does not have anything to do with how the JIT compiler will transform these function calls to assembly code, which often uses processor registers to hold function arguments.
As with object creation, first create a template method to use as a basis for the IL. Since we will have to call this method with just an object parameter (that is all we will have in our program), the function parameters specify the extension as just an object. This means we will have to cast it to the right type before calling DoWork. In the template, we have hard-coded type information, but in the generator we can get the type information programmatically.
static bool CallMethodTemplate(object extensionObj, string argument) { var extension = (DynamicLoadExtension.Extension)extensionObj; return extension.DoWork(argument); }
The resulting IL for this template looks like:
.locals init ( [0] class [DynamicLoadExtension]DynamicLoadExtension.Extension extension ) IL_0000: ldarg.0 IL_0001: castclass [DynamicLoadExtension]DynamicLoadExtension.Extension IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldarg.1 IL_0009: callvirt instance bool [DynamicLoadExtension]DynamicLoadExtension.Extension::DoWork(string) IL_000e: ret
Notice there is a local variable declared. This holds the result of the cast. We will see later that it can be optimized away. This IL leads to a straightforward translation into a DynamicMethod:
private static T GenerateMethodCallDelegate<T>( MethodInfo methodInfo, Type extensionType, Type returnType, Type[] parameterTypes) where T : class { var dynamicMethod = new DynamicMethod("Invoke_" + methodInfo.Name, returnType, parameterTypes, true); var ilGenerator = dynamicMethod.GetILGenerator(); ilGenerator.DeclareLocal(extensionType); // object's this parameter ilGenerator.Emit(OpCodes.Ldarg_0); // cast it to the correct type ilGenerator.Emit(OpCodes.Castclass, extensionType); // actual method argument ilGenerator.Emit(OpCodes.Stloc_0); ilGenerator.Emit(OpCodes.Ldloc_0); ilGenerator.Emit(OpCodes.Ldarg_1); ilGenerator.EmitCall(OpCodes.Callvirt, methodInfo, null); ilGenerator.Emit(OpCodes.Ret); object del = dynamicMethod.CreateDelegate(typeof(T)); return (T)del; }
To generate the dynamic method, we need the MethodInfo, looked up from the extension’s Type object. We also need the Type of the return object and the Types of all the parameters to the method, including the implicit this parameter (which is the same as extensionType).
This method works perfectly, but look closely at what it is doing and recall the stack-based nature of IL instructions. Here’s how this method works:
1. Declare local variable
2. Push arg0 (the this pointer) onto the stack (LdArg_0)
3. Cast arg0 to the right type and push result onto the stack
4. Pop the top of the stack and store it in the local variable (Stloc_0)
5. Push the local variable onto the stack (Ldloc_0)
6. Push arg1 (the string argument) onto the stack
7. Call the DoWork method (Callvirt)
8. Return
There is some glaring redundancy in there, specifically with the local variable. We have the casted object on the stack, we pop it off then push it right back on. We could optimize this IL by just removing everything having to do with the local variable. It is possible that the JIT compiler would optimize this away for us anyway, but doing the optimization does not hurt, and could help if we have hundreds or thousands dynamic methods, all of which will need to be JITted. The other optimization is to recognize that the Callvirt opcode can be changed to a simpler Call opcode because we know there is no virtual method here. Now our IL looks like this:
var ilGenerator = dynamicMethod.GetILGenerator(); // object's this parameter ilGenerator.Emit(OpCodes.Ldarg_0); // cast it to the correct type ilGenerator.Emit(OpCodes.Castclass, extensionType); // actual method argument ilGenerator.Emit(OpCodes.Ldarg_1); ilGenerator.EmitCall(OpCodes.Call, methodInfo, null); ilGenerator.Emit(OpCodes.Ret);
To use our delegate, we just need to call it like this:
Func<object, string, bool> doWorkDel = GenerateMethodCallDelegate< Func<object, string, bool>>( methodInfo, type, typeof(bool), new Type[] { typeof(object), typeof(string) }); bool result = doWorkDel(extension, argument);
Using direct method calls as a baseline, you can see that the reflection methods are much worse. Our generated code does not quite bring it back, but it is close. These numbers are for a function call that does not actually do anything, so they represent pure overhead of the function call, which is not a very realistic situation. If I add some minimal work (string parsing and a square root calculation), the numbers change a little:
In the end, this demonstrates that if you rely on Activator.CreateInstance or MethodInfo.Invoke, you can significantly benefit from some code generation.
You can use code generation for other things as well. If your application does a lot of string interpretation or has a state machine of any kind, this is a good candidate for code generation. .NET itself does this with regular expressions (See Chapter 6) and XML serialization.
Excerpt from the book : Writing high performance .NET Code written by Ben Watson
For all your application development needs, visit www.verbat.com for a fiscally conscious proposal that meets your needs ( So I can keep this blog going as well!!!!)
Alternatively click through the link if you found this article interesting. (This will help the companies Search engine rankings)
Leave a Reply