I am developing a program that uses DynamicMethod
quite a lot, and found that running it under Release mode is significantly slower than under Debug mode. I managed to repro the problem with the following small snippet.
using System.Reflection;
using System.Reflection.Emit;
using System.Diagnostics;
public class Foo
{
private static int Count = 0;
public static void Increment()
{
Interlocked.Increment(ref Count);
}
public static int MyCount => Count;
}
public class Test
{
private delegate void MyDelegate();
private static MyDelegate Generate()
{
DynamicMethod test = new("test", null, Array.Empty<Type>());
MethodInfo? m = typeof(Foo).GetMethod("Increment", Array.Empty<Type>());
if (m == null) { throw new Exception("!!!"); }
ILGenerator il = test.GetILGenerator(256);
// By putting more EmitCalls, we see more differences
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.EmitCall(OpCodes.Call, m, null);
il.Emit(OpCodes.Ret);
return (MyDelegate) test.CreateDelegate(typeof(MyDelegate));
}
public static void Main()
{
Stopwatch sw = new();
MyDelegate f = Generate();
sw.Start();
f();
sw.Stop();
Console.WriteLine("Time = {0:F6}ms", sw.Elapsed.TotalSeconds);
}
}
When I run the above program in Debug mode and Release mode, the call takes about 0.0005ms and 0.0007ms, respectively. And of course, by making more EmitCalls, I can easily make it twice slower or more.
I am currently using .NET 6, and I see consistent behaviors in Windows, Linux, and macOS:
dotnet --version
6.0.203
I also tried to add GC.Collect
before sw.Start()
just to make sure that GC is not affecting the performance behavior. But I see the same differences. Am I missing anything here? Why is it slower in the Release mode?
@Hans answered in the comment that this is because JITting in the Release mode is slower than in the Debug mode due to extra optimizations.
I still would like to know if there is a way to turn off the optimizations specifically for DynamicMethod
s (while still being in the Release mode) because the jitting cost seems too high compared to the gain that I can get by repeatedly running the DynamicMethod.