繁体   English   中英

DynamicMethod 在 Release 模式下比在 Debug 模式下慢

[英]DynamicMethod is slower in Release mode than Debug mode

我正在开发一个经常使用DynamicMethod的程序,发现在 Release 模式下运行它比在 Debug 模式下运行要慢得多。 我设法用下面的小片段重现了这个问题。

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);
    }
}

当我在 Debug 模式和 Release 模式下运行上述程序时,调用分别需要大约 0.0005ms 和 0.0007ms。 当然,通过制作更多的 EmitCall,我可以轻松地让它慢两倍或更多。

我目前使用的是 .NET 6,并且在 Windows、Linux 和 macOS 中看到了一致的行为:

dotnet --version
6.0.203

我还尝试在sw.Start()之前添加GC.Collect ,以确保 GC 不会影响性能行为。 但我看到了同样的差异。 我在这里错过了什么吗? 为什么在 Release 模式下比较慢?


@Hans 在评论中回答说,这是因为由于额外的优化,在发布模式下的 JITting 比在调试模式下慢。

我仍然想知道是否有办法关闭专门针对DynamicMethod的优化(同时仍处于 Release 模式),因为与重复运行 DynamicMethod 可以获得的收益相比,jitting 成本似乎太高了。

此答案针对您更新的问题。 我尝试了您的代码并收到了类似的结果。 为两者添加MethodImplAttribute ,您的GenerateIncrement方法会导致我机器上的发布配置的结果几乎相同。

[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public static void Increment() { ... }

[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
private static MyDelegate Generate() { ... }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM