gpt4 book ai didi

c# - 好奇心 : Why does Expression<. ..> 编译时运行速度比最小 DynamicMethod 快?

转载 作者:IT王子 更新时间:2023-10-29 03:48:34 25 4
gpt4 key购买 nike

我目前正在进行一些最后的优化,主要是为了娱乐和学习,并发现了一些给我留下几个问题的东西。

首先是问题:

  1. 当我通过使用 DynamicMethod 在内存中构建方法时,并使用调试器,在反汇编 View 中查看代码时,我有什么办法可以进入生成的汇编代码吗?调试器似乎只是为我跳过了整个方法
  2. 或者,如果这不可能,我能否以某种方式将生成的 IL 代码作为程序集保存到磁盘,以便我可以使用 Reflector 检查它? ?
  3. 为什么 Expression<...>我的简单加法版本 (Int32+Int32 => Int32) 比最小 DynamicMethod 版本运行得更快?

这是一个简短而完整的演示程序。在我的系统上,输出是:

DynamicMethod: 887 ms
Lambda: 1878 ms
Method: 1969 ms
Expression: 681 ms

我预计 lambda 和方法调用具有更高的值,但 DynamicMethod 版本始终慢 30-50%(变化可能是由于 Windows 和其他程序)。有谁知道原因吗?

程序如下:

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

最佳答案

通过DynamicMethod 创建的方法通过两个 thunk,而通过 Expression<> 创建的方法没有经过任何。

这是它的工作原理。这是调用 fn(0, 1) 的调用序列在Time方法(为了便于调试,我将参数硬编码为 0 和 1):

00cc032c 6a01            push    1           // 1 argument
00cc032e 8bcf mov ecx,edi
00cc0330 33d2 xor edx,edx // 0 argument
00cc0332 8b410c mov eax,dword ptr [ecx+0Ch]
00cc0335 8b4904 mov ecx,dword ptr [ecx+4]
00cc0338 ffd0 call eax // 1 arg on stack, two in edx, ecx

对于我调查的第一次调用,DynamicMethod , call eax线出现像这样:

00cc0338 ffd0            call    eax {003c2084}
0:000> !u 003c2084
Unmanaged code
003c2084 51 push ecx
003c2085 8bca mov ecx,edx
003c2087 8b542408 mov edx,dword ptr [esp+8]
003c208b 8b442404 mov eax,dword ptr [esp+4]
003c208f 89442408 mov dword ptr [esp+8],eax
003c2093 58 pop eax
003c2094 83c404 add esp,4
003c2097 83c010 add eax,10h
003c209a ff20 jmp dword ptr [eax]

这似乎是在进行一些堆栈调整以重新排列参数。我推测这是由于使用隐式“this”参数的代表与不使用隐式“this”参数的代表之间存在差异。

最后的跳转像这样解决:

003c209a ff20            jmp     dword ptr [eax]      ds:0023:012f7edc=0098c098
0098c098 e963403500 jmp 00ce0100

0098c098 处的剩余代码看起来像 JIT thunk,其开始被重写为 jmp在 JIT 之后。只有在这个跳转之后我们才能看到真正的代码:

0:000> !u eip
Normal JIT generated code
DynamicClass.TestMethod(Int32, Int32)
Begin 00ce0100, size 5
>>> 00ce0100 03ca add ecx,edx
00ce0102 8bc1 mov eax,ecx
00ce0104 c3 ret

通过 Expression<> 创建的方法的调用顺序是不同的 - 它缺少堆栈调配代码。在这里,从第一次跳转eax :

00cc0338 ffd0            call    eax {00ce00a8}

0:000> !u eip
Normal JIT generated code
DynamicClass.lambda_method(System.Runtime.CompilerServices.ExecutionScope, Int32, Int32)
Begin 00ce00a8, size b
>>> 00ce00a8 8b442404 mov eax,dword ptr [esp+4]
00ce00ac 03d0 add edx,eax
00ce00ae 8bc2 mov eax,edx
00ce00b0 c20400 ret 4

现在,事情是怎么变成这样的?

  1. 堆栈调整不是必需的(实际上使用了来自委托(delegate)的隐式第一个参数,即不像委托(delegate)绑定(bind)到静态方法)
  2. JIT 一定是由 LINQ 编译逻辑强制执行的,以便委托(delegate)持有真实的目标地址而不是伪造的地址。

我不知道 LINQ 如何强制执行 JIT,但我知道如何自己强制执行 JIT - 通过至少调用函数一次。更新:我找到了另一种强制执行 JIT 的方法:使用 restrictedSkipVisibility将参数传递给构造函数并传递 true .因此,这里是修改后的代码,通过使用隐式“this”参数消除堆栈困惑,并使用备用构造函数进行预编译,以便绑定(bind)地址是真实地址,而不是 thunk:

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(object), typeof(Int32),
typeof(Int32) }, true);
var il = method.GetILGenerator();

il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ret);

Func<Int32, Int32, Int32> f1 =
(Func<Int32, Int32, Int32>)method.CreateDelegate(
typeof(Func<Int32, Int32, Int32>), null);
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)
Console.WriteLine(name + ": " + sw.ElapsedMilliseconds + " ms");
}

private static Int32 Sum(Int32 a, Int32 b)
{
return a + b;
}
}
}

这是我系统上的运行时:

DynamicMethod: 312 ms
Lambda: 417 ms
Method: 417 ms
Expression: 312 ms

更新添加:

我尝试在我的新系统上运行这段代码,这是一个运行 Windows 7 x64 并安装了 .NET 4 beta 2(mscoree.dll 版本 4.0.30902)的 Core i7 920,结果是可变的。

csc 3.5, /platform:x86, runtime v2.0.50727 (via .config)

Run #1
DynamicMethod: 214 ms
Lambda: 571 ms
Method: 570 ms
Expression: 249 ms

Run #2
DynamicMethod: 463 ms
Lambda: 392 ms
Method: 392 ms
Expression: 463 ms

Run #3
DynamicMethod: 463 ms
Lambda: 570 ms
Method: 570 ms
Expression: 463 ms

也许这是影响结果的 Intel SpeedStep,或者可能是 Turbo Boost。无论如何,这很烦人。

csc 3.5, /platform:x64, runtime v2.0.50727 (via .config)
DynamicMethod: 428 ms
Lambda: 392 ms
Method: 392 ms
Expression: 428 ms

csc 3.5, /platform:x64, runtime v4
DynamicMethod: 428 ms
Lambda: 356 ms
Method: 356 ms
Expression: 428 ms

csc 4, /platform:x64, runtime v4
DynamicMethod: 428 ms
Lambda: 356 ms
Method: 356 ms
Expression: 428 ms

csc 4, /platform:x86, runtime v4
DynamicMethod: 463 ms
Lambda: 570 ms
Method: 570 ms
Expression: 463 ms

csc 3.5, /platform:x86, runtime v4
DynamicMethod: 214 ms
Lambda: 570 ms
Method: 571 ms
Expression: 249 ms

这些结果中的许多将是计时意外,无论是什么导致了 C# 3.5/runtime v2.0 场景中的随机加速。我将不得不重新启动以查看 SpeedStep 或 Turbo Boost 是否对这些影响负责。

关于c# - 好奇心 : Why does Expression<. ..> 编译时运行速度比最小 DynamicMethod 快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1296683/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com