令人惊讶的是简单的C#程序性能不同

Ima*_*ler 20 c# performance

下面是一个简单的程序,只需很小的改动,就会产生重大的性能影响,我不明白为什么.

程序所做的并不是真正相关的,但它通过计算两个不同质量的物体和一个墙壁之间的碰撞,以非常复杂的方式计算PI.在我改变代码时我注意到的是性能差异很大.

有问题的行是注释的行,它们在数学上是等价的.使用慢速版本会使整个程序的使用速度大约是使用快速版本的两倍.

int iterations = 0;

for (int i = 4; i < 9; i++)
{
    Stopwatch s = Stopwatch.StartNew();

    double ms = 1.0;
    double mL = Math.Pow(100.0, i);
    double uL = 1.0;
    double us = 0.0;
    double msmLInv = 1d / (ms + mL);

    long collisions = 0;
    while (!(uL < 0 && us <= 0 && uL <= us))
    {
        Debug.Assert(++iterations > 0);
        ++collisions;

        double vs = (2 * mL * uL + us * (ms - mL)) * msmLInv;

        //double vL = (2 * ms * us - uL * (ms - mL)) * msmLInv; //fast
        double vL = uL + (us - vs) / mL; //slow


        Debug.Assert(Math.Abs(((2 * ms * us - uL * (ms - mL)) * msmLInv) - (uL + (us - vs) / mL)) < 0.001d); //checks equality between fast and slow
        if (vs > 0)
        {
            ++collisions;
            vs = -vs;
        }

        us = vs;
        uL = vL;
    }

    s.Stop();


    Debug.Assert(collisions.ToString() == "314159265359".Substring(0, i + 1)); //check the correctness
    Console.WriteLine($"i: {i}, T: {s.ElapsedMilliseconds / 1000f}, PI: {collisions}");
}

Debug.Assert(iterations == 174531180); //check that we dont skip loops

Console.Write("Waiting...");
Console.ReadKey();
Run Code Online (Sandbox Code Playgroud)

我的直觉说,因为快速版本有7个操作,而慢速版本有4个操作,慢速版本应该更快,但事实并非如此.

我使用.NET Reflector反汇编程序,除了下面显示的部分外,它表明它们大致相同,正如预期的那样.相同之前和之后的代码

//slow
ldloc.s uL
ldloc.2 
ldloc.s us
ldloc.s vs
sub 
mul 
ldloc.3 
div 
add 
Run Code Online (Sandbox Code Playgroud)
//fast
ldc.r8 2
ldloc.2 
mul 
ldloc.s us
mul 
ldloc.s uL
ldloc.2 
ldloc.3 
sub 
mul 
sub 
ldloc.2 
ldloc.3 
add 
div 
Run Code Online (Sandbox Code Playgroud)

这也表明更多的代码正在使用快速版本执行,这也会让我预期它会变慢.

我现在唯一的猜测是慢速版本会导致更多缓存未命中,但我不知道如何衡量它(欢迎指南).除此之外,我不知所措.


编辑1.根据@EricLippert的请求,这里是来自JIT的内部while循环的反汇编,其中差异是.

编辑2.解决了如何打破发布程序并更新反汇编所以现在似乎有一些区别.我通过运行发布版本获得了这些结果,使用ReadKey在同一函数中停止程序,附加调试器,使程序继续执行,打破下一行,进入反汇编窗口(ctrl + alt + d)

编辑3.根据所有建议将代码更改为更新的示例.

//slow
    78: 
    79:                     vs = (2 * mL * uL + us * (ms - mL)) / (ms + mL);
00C10530  call        CA9AD013  
00C10535  fdiv        st,st(3)  
00C10537  faddp       st(2),st  
    80: 
    81:                     //double vL = (2 * ms * us - uL * (ms - mL)) / (ms + mL); //fast
    82:                     double vL = uL + ms * (us - vs) / mL; //slow
00C10539  fldz  
00C1053B  fcomip      st,st(1)  
00C1053D  jp          00C10549  
00C1053F  jae         00C10549  
00C10541  add         ebx,1  
00C10544  adc         edi,0  
00C10547  fchs  
00C10549  fld         st(1)  
    73: 
    74:                 while (!(uL < 0 && us <= 0 && uL <= us))
00C1054B  fldz  
00C1054D  fcomip      st,st(3)  
00C1054F  fstp        st(2)  
00C10551  jp          00C10508  
00C10553  jbe         00C10508  
00C10555  fldz  
00C10557  fcomip      st,st(1)  
00C10559  jp          00C10508  
00C1055B  jb          00C10508  
00C1055D  fxch        st(1)  
00C1055F  fcomi       st,st(1)  
00C10561  jnp         00C10567  
00C10563  fxch        st(1)  
00C10565  jmp         00C10508  
00C10567  jbe         00C1056D  
00C10569  fxch        st(1)  
00C1056B  jmp         00C10508  
00C1056D  fstp        st(1)  
00C1056F  fstp        st(0)  
00C10571  fstp        st(0)  
    92:                 }
    93: 
    94:                 s.Stop();
00C10573  mov         ecx,esi  
00C10575  call        71880260  
    95: 
    96:                 Console.WriteLine($"i: {i}, T: {s.ElapsedMilliseconds / 1000f}, PI: {collisions}");
00C1057A  mov         ecx,725B0994h  
00C1057F  call        00B930C8  
00C10584  mov         edx,eax  
00C10586  mov         eax,dword ptr [ebp-14h]  
00C10589  mov         dword ptr [edx+4],eax  
00C1058C  mov         dword ptr [ebp-34h],edx  
00C1058F  mov         ecx,725F3778h  
00C10594  call        00B930C8  
00C10599  mov         dword ptr [ebp-38h],eax  
00C1059C  mov         ecx,725F2C10h  
00C105A1  call        00B930C8  
00C105A6  mov         dword ptr [ebp-3Ch],eax  
00C105A9  mov         ecx,esi  
00C105AB  call        71835820  
00C105B0  push        edx  
00C105B1  push        eax  
00C105B2  push        0  
00C105B4  push        2710h  
00C105B9  call        736071A0  
00C105BE  mov         dword ptr [ebp-48h],eax  
00C105C1  mov         dword ptr [ebp-44h],edx  
00C105C4  fild        qword ptr [ebp-48h]  
00C105C7  fstp        dword ptr [ebp-40h]  
00C105CA  fld         dword ptr [ebp-40h]  
00C105CD  fdiv        dword ptr ds:[0C10678h]  
00C105D3  mov         eax,dword ptr [ebp-38h]  
00C105D6  fstp        dword ptr [eax+4]  
00C105D9  mov         edx,dword ptr [ebp-38h]  
00C105DC  mov         eax,dword ptr [ebp-3Ch]  
00C105DF  mov         dword ptr [eax+4],ebx  
00C105E2  mov         dword ptr [eax+8],edi  
00C105E5  mov         esi,dword ptr [ebp-3Ch]  
00C105E8  lea         edi,[ebp-30h]  
00C105EB  xorps       xmm0,xmm0  
00C105EE  movq        mmword ptr [edi],xmm0  
00C105F2  movq        mmword ptr [edi+8],xmm0  
00C105F7  push        edx  
00C105F8  push        esi  
00C105F9  lea         ecx,[ebp-30h]  
00C105FC  mov         edx,dword ptr [ebp-34h]  
00C105FF  call        724A2ED4  
00C10604  lea         eax,[ebp-30h]  
00C10607  push        dword ptr [eax+0Ch]  
00C1060A  push        dword ptr [eax+8]  
00C1060D  push        dword ptr [eax+4]  
00C10610  push        dword ptr [eax]  
00C10612  mov         edx,dword ptr ds:[3832310h]  
00C10618  xor         ecx,ecx  
00C1061A  call        72497A00  
00C1061F  mov         ecx,eax  
00C10621  call        72571934  
    61:             for (int i = 4; i < 9; i++)
00C10626  inc         dword ptr [ebp-14h]  
00C10629  cmp         dword ptr [ebp-14h],9  
00C1062D  jl          00C10496  
    97:             }
    98: 
    99:             Console.WriteLine(loops);
00C10633  mov         ecx,dword ptr [ebp-10h]  
00C10636  call        72C583FC  
   100:             Console.Write("Waiting...");
00C1063B  mov         ecx,dword ptr ds:[3832314h]  
00C10641  call        724C67F0  
00C10646  lea         ecx,[ebp-20h]  
00C10649  xor         edx,edx  
00C1064B  call        72C57984  
00C10650  lea         esp,[ebp-0Ch]  
00C10653  pop         ebx  
00C10654  pop         esi  
00C10655  pop         edi  
00C10656  pop         ebp  
00C10657  ret  
Run Code Online (Sandbox Code Playgroud)
//fast
 80: 
    81:                     double vL = (2 * ms * us - uL * (ms - mL)) / (ms + mL); //fast
02FD0550  or          al,83h  
    80: 
    81:                     double vL = (2 * ms * us - uL * (ms - mL)) / (ms + mL); //fast
02FD0552  ret  
02FD0553  add         dword ptr [ebx-3626FF29h],eax  
02FD0559  fchs  
02FD055B  fxch        st(1)  
02FD055D  fld         st(0)  
    73: 
    74:                 while (!(uL < 0 && us <= 0 && uL <= us))
02FD055F  fldz  
02FD0561  fcomip      st,st(2)  
02FD0563  fstp        st(1)  
02FD0565  jnp         02FD056B  
02FD0567  fxch        st(1)  
02FD0569  jmp         02FD050B  
02FD056B  ja          02FD0571  
02FD056D  fxch        st(1)  
02FD056F  jmp         02FD050B  
02FD0571  fldz  
02FD0573  fcomip      st,st(2)  
02FD0575  jnp         02FD057B  
02FD0577  fxch        st(1)  
02FD0579  jmp         02FD050B  
02FD057B  jae         02FD0581  
02FD057D  fxch        st(1)  
02FD057F  jmp         02FD050B  
02FD0581  fcomi       st,st(1)  
02FD0583  jnp         02FD0589  
02FD0585  fxch        st(1)  
02FD0587  jmp         02FD050B  
02FD0589  jbe         02FD0592  
02FD058B  fxch        st(1)  
02FD058D  jmp         02FD050B  
02FD0592  fstp        st(1)  
02FD0594  fstp        st(0)  
    92:                 }
    93: 
    94:                 s.Stop();
02FD0596  mov         ecx,esi  
02FD0598  call        71880260  
    95: 
    96:                 Console.WriteLine($"i: {i}, T: {s.ElapsedMilliseconds / 1000f}, PI: {collisions}");
02FD059D  mov         ecx,725B0994h  
02FD05A2  call        013830C8  
02FD05A7  mov         edx,eax  
02FD05A9  mov         eax,dword ptr [ebp-14h]  
02FD05AC  mov         dword ptr [edx+4],eax  
02FD05AF  mov         dword ptr [ebp-3Ch],edx  
02FD05B2  mov         ecx,725F3778h  
02FD05B7  call        013830C8  
02FD05BC  mov         dword ptr [ebp-40h],eax  
02FD05BF  mov         ecx,725F2C10h  
02FD05C4  call        013830C8  
02FD05C9  mov         dword ptr [ebp-44h],eax  
02FD05CC  mov         ecx,esi  
02FD05CE  call        71835820  
02FD05D3  push        edx  
02FD05D4  push        eax  
02FD05D5  push        0  
02FD05D7  push        2710h  
02FD05DC  call        736071A0  
02FD05E1  mov         dword ptr [ebp-50h],eax  
02FD05E4  mov         dword ptr [ebp-4Ch],edx  
02FD05E7  fild        qword ptr [ebp-50h]  
02FD05EA  fstp        dword ptr [ebp-48h]  
02FD05ED  fld         dword ptr [ebp-48h]  
02FD05F0  fdiv        dword ptr ds:[2FD06A8h]  
02FD05F6  mov         eax,dword ptr [ebp-40h]  
02FD05F9  fstp        dword ptr [eax+4]  
02FD05FC  mov         edx,dword ptr [ebp-40h]  
02FD05FF  mov         eax,dword ptr [ebp-44h]  
02FD0602  mov         dword ptr [eax+4],ebx  
02FD0605  mov         dword ptr [eax+8],edi  
02FD0608  mov         esi,dword ptr [ebp-44h]  
02FD060B  lea         edi,[ebp-38h]  
02FD060E  xorps       xmm0,xmm0  
02FD0611  movq        mmword ptr [edi],xmm0  
02FD0615  movq        mmword ptr [edi+8],xmm0  
02FD061A  push        edx  
02FD061B  push        esi  
02FD061C  lea         ecx,[ebp-38h]  
02FD061F  mov         edx,dword ptr [ebp-3Ch]  
02FD0622  call        724A2ED4  
02FD0627  lea         eax,[ebp-38h]  
02FD062A  push        dword ptr [eax+0Ch]  
02FD062D  push        dword ptr [eax+8]  
02FD0630  push        dword ptr [eax+4]  
02FD0633  push        dword ptr [eax]  
02FD0635  mov         edx,dword ptr ds:[4142310h]  
02FD063B  xor         ecx,ecx  
02FD063D  call        72497A00  
02FD0642  mov         ecx,eax  
02FD0644  call        72571934  
    61:             for (int i = 4; i < 9; i++)
02FD0649  inc         dword ptr [ebp-14h]  
02FD064C  cmp         dword ptr [ebp-14h],9  
02FD0650  jl          02FD0496  
    97:             }
    98: 
    99:             Console.WriteLine(loops);
02FD0656  mov         ecx,dword ptr [ebp-10h]  
02FD0659  call        72C583FC  
   100:             Console.Write("Waiting...");
02FD065E  mov         ecx,dword ptr ds:[4142314h]  
02FD0664  call        724C67F0  
02FD0669  lea         ecx,[ebp-28h]  
02FD066C  xor         edx,edx  
02FD066E  call        72C57984  
02FD0673  lea         esp,[ebp-0Ch]  
02FD0676  pop         ebx  
02FD0677  pop         esi  
02FD0678  pop         edi  
02FD0679  pop         ebp  
02FD067A  ret  
Run Code Online (Sandbox Code Playgroud)

M.k*_*ary 9

我认为原因是CPU 指令流水线.你的慢速方程取决于vs,这意味着vs必须首先计算,然后vl计算.

但是在你的快速等式中,更多的指令可以被流水线化,vs并且vl可以同时计算,因为它们不相互依赖.

请不要将此与多线程混淆.指令流水线是在非常低的硬件级别实现的一些东西,并试图同时利用尽可能多的CPU模块以实现最大的指令吞吐量.


Ald*_*ert 5

你的计算不相等

double vL = (2 * ms * us - uL * (ms - mL)) / (ms + mL); //fast
double vL = uL + ms * (us - vs) / mL; //slow
Run Code Online (Sandbox Code Playgroud)

示例:我错过vs了快速版本

我希望你的while循环因为这个而做更多的迭代?

  • @EricLippert Math.Abs​​(vL1 - vL2)> 0.001f永远不会成立 (2认同)