[英]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 ,您的Generate
和Increment
方法会导致我机器上的发布配置的结果几乎相同。
[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.