I'm currently doing some last-measure optimizations, mostly for fun and learning, and discovered something that left me with a couple of questions.
First, the questions:
- When I construct a method in-memory through the use of DynamicMethod, and use the debugger, is there any way for me to step into the generated assembly code, when vieweing the code in the disassembler view? The debugger seems to just step over the whole method for me
- Or, if that's not possible, is it possible for me to somehow save the generated IL code to disk as an assembly, so that I can inspect it with Reflector?
- Why does the
Expression<...>
version of my simple addition method (Int32+Int32 => Int32) run faster than a minimal DynamicMethod version?
Here's a short and complete program that demonstrates. On my system, the output is:
DynamicMethod: 887 ms
Lambda: 1878 ms
Method: 1969 ms
Expression: 681 ms
I expected the lambda and method calls to have higher values, but the DynamicMethod version is consistently about 30-50% slower (variations probably due to Windows and other programs). Anyone know the reason?
Here's the program:
using System;
using System.Linq.Expressions;
using System.Reflection.Emit;
using System.Diagnostics;
namespace Sandbox
{
public class Program
{
public static void Main(String[] args)
{
DynamicMethod method = new DynamicMethod("TestMethod",
typeof(Int32), new Type[] { typeof(Int32), typeof(Int32) });
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ret);
Func<Int32, Int32, Int32> f1 =
(Func<Int32, Int32, Int32>)method.CreateDelegate(
typeof(Func<Int32, Int32, Int32>));
Func<Int32, Int32, Int32> f2 = (Int32 a, Int32 b) => a + b;
Func<Int32, Int32, Int32> f3 = Sum;
Expression<Func<Int32, Int32, Int32>> f4x = (a, b) => a + b;
Func<Int32, Int32, Int32> f4 = f4x.Compile();
for (Int32 pass = 1; pass <= 2; pass++)
{
// Pass 1 just runs all the code without writing out anything
// to avoid JIT overhead influencing the results
Time(f1, "DynamicMethod", pass);
Time(f2, "Lambda", pass);
Time(f3, "Method", pass);
Time(f4, "Expression", pass);
}
}
private static void Time(Func<Int32, Int32, Int32> fn,
String name, Int32 pass)
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (Int32 index = 0; index <= 100000000; index++)
{
Int32 result = fn(index, 1);
}
sw.Stop();
if (pass == 2)
Debug.WriteLine(name + ": " + sw.ElapsedMilliseconds + " ms");
}
private static Int32 Sum(Int32 a, Int32 b)
{
return a + b;
}
}
}
Debug.WriteLine
looks out of place; but even withConsole.WriteLine
my stats are similar: DynamicMethod: 630 ms Lambda: 561 ms Method: 553 ms Expression: 360 ms I'm still looking... – NorthamptonrestrictedSkipVisibility
DynamicMethod constructor argument. Depending on context (code security), it might not be available though. – Constituteresult
inInt32 result = fn(index, 1);
affects the result? Dead code optimization should kick in right? I tried printing result as well, and I get DynamicMethod: ~1000 ms Lambda: ~400 ms Method: ~1000 ms Expression: ~400 ms. For me, results of (DynamicMethod and Method) and (Lambda and Expression) always aligned, unlike results here which align (DynamicMethod and Expression) and (Lambda and Method). Strange.. using C# 6.0, .NET 4.6, Any CPU on a 64 bit machine. – Grendel