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 DynamicMethods (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.
 
    