为什么添加局部变量会使.NET代码变慢

Edw*_*rey 43 .net c# compiler-construction performance jit

为什么要注释这个for循环的前两行,并在42%的加速时取消注释第三个结果?

int count = 0;
for (uint i = 0; i < 1000000000; ++i) {
    var isMultipleOf16 = i % 16 == 0;
    count += isMultipleOf16 ? 1 : 0;
    //count += i % 16 == 0 ? 1 : 0;
}
Run Code Online (Sandbox Code Playgroud)

在时序背后是非常不同的汇编代码:循环中的13对7指令.该平台是运行.NET 4.0 x64的Windows 7.启用了代码优化,测试应用程序在VS2010之外运行.[ 更新: Repro项目,对验证项目设置很有用.]

消除中间布尔值是一个基本的优化,是我1980年代龙书中最简单的一个.在生成CIL或JITing x64机器代码时,如何不应用优化?

有没有"真正的编译器,我希望你优化这段代码,请"切换?虽然我同情过早优化类似于对金钱热爱的情绪,但我可以看到试图描述一个复杂算法的挫败感,这个算法在整个惯例中分散.你可以通过热点工作,但没有暗示更广泛的温暖区域可以通过手动调整我们通常认为理所当然的编译器来大大改善.我当然希望我在这里遗漏一些东西.

更新: x86也会出现速度差异,但取决于方法即时编译的顺序.请参阅为什么JIT订单会影响性能?

汇编代码(根据要求):

    var isMultipleOf16 = i % 16 == 0;
00000037  mov         eax,edx 
00000039  and         eax,0Fh 
0000003c  xor         ecx,ecx 
0000003e  test        eax,eax 
00000040  sete        cl 
    count += isMultipleOf16 ? 1 : 0;
00000043  movzx       eax,cl 
00000046  test        eax,eax 
00000048  jne         0000000000000050 
0000004a  xor         eax,eax 
0000004c  jmp         0000000000000055 
0000004e  xchg        ax,ax 
00000050  mov         eax,1 
00000055  lea         r8d,[rbx+rax] 
Run Code Online (Sandbox Code Playgroud)
    count += i % 16 == 0 ? 1 : 0;
00000037  mov         eax,ecx 
00000039  and         eax,0Fh 
0000003c  je          0000000000000042 
0000003e  xor         eax,eax 
00000040  jmp         0000000000000047 
00000042  mov         eax,1 
00000047  lea         edx,[rbx+rax] 
Run Code Online (Sandbox Code Playgroud)

Mac*_*iej 9

问题应该是"我为什么在我的机器上看到这样的差异?".我不能重现如此巨大的速度差异,并怀疑你的环境有特定的东西.很难说它可能是什么.可以是你前一段时间设置的一些(编译器)选项而忘记它们.

我创建了一个控制台应用程序,在发布模式下重建(x86)并在VS外部运行.结果几乎相同,两种方法均为1.77秒.这是确切的代码:

static void Main(string[] args)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    int count = 0;

    for (uint i = 0; i < 1000000000; ++i)
    {
        // 1st method
        var isMultipleOf16 = i % 16 == 0;
        count += isMultipleOf16 ? 1 : 0;

        // 2nd method
        //count += i % 16 == 0 ? 1 : 0;
    }

    sw.Stop();
    Console.WriteLine(string.Format("Ellapsed {0}, count {1}", sw.Elapsed, count));
    Console.ReadKey();
}
Run Code Online (Sandbox Code Playgroud)

请任何有5分钟复制代码,重建,在VS外部运行并在结果中发布评论的人.我想避免说"它适用于我的机器".

编辑

为了确保我已经创建了64位 Winforms应用程序,结果与问题类似 - 第一种方法比第二种方法(1.05秒)(1.57秒).我观察到的差异是33% - 仍然很多.似乎.NET4 64位JIT编译器中存在一个错误.

  • @EdwardBrey运行测试会让我在两个平台上的Multiline版本上执行速度变慢.但是,如果我更改测试以使其运行4xMultiline然后运行4xSingleline,则x86上没有速度差异(x64不受影响) (3认同)
  • @EdwardBrey,我只能在64位应用程序中重现它 (2认同)

Edw*_*rey 3

这是 .NET Framework 中的一个错误。

好吧,实际上我只是猜测,但我在Microsoft Connect上提交了一份错误报告来看看他们怎么说。在 Microsoft 删除该报告后,我在 GitHub 上的roslyn项目上重新提交了该报告。

更新: Microsoft 已将该问题移至coreclr项目。从对该问题的评论来看,称其为 bug 似乎有点过分了;这更多的是缺少优化。

  • 如果每次有程序员告诉我“我的代码不起作用。这一定是框架(或编译器或运行时库等)中的错误”,然后后来发现这是一个错误,我就能得到一美元按照他自己的准则,我可以退休。 (9认同)