c# - 简单 C# 程序的惊人不同性能

标签 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();

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

我使用 .NET Reflector 反汇编了该程序,结果表明它们与预期的一样,除了下面所示的部分外,大部分都是相同的。前后代码一模一样

//slow
ldloc.s uL
ldloc.2 
ldloc.s us
ldloc.s vs
sub 
mul 
ldloc.3 
div 
add 

//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 

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

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


编辑 1。 根据@EricLippert 的要求,这里是内部 while 循环的 JIT 反汇编,不同之处在于。

编辑 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  

//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  

最佳答案

我认为原因是 CPU instruction pipelining .你的慢方程取决于vs,这意味着必须先计算vs,然后计算vl

但是在您的快速等式中,可以流水线化更多指令,因为 vsvl 可以同时计算,因为它们不相互依赖。

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

关于c# - 简单 C# 程序的惊人不同性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54188731/

相关文章:

c# - 将字符串作为 C# 代码运行的最佳方法

c# - SQL Server : Event does not reference any tables (Tuning Advisor warning)

android - Realm 惰性查询 - 它们比 OrmLite 更快吗?

java - 一个类实现两个或多个接口(interface)是一个好习惯吗?

c# - 具有过滤 dbContext 的 Multi-Tenancy Web 应用程序

c# - 所有内置 .Net 属性

c# - 如何替换 MEF 容器中导出的部件/对象?

c# - 如何使用反射从事件中获取底层委托(delegate)列表?

相同算法的 Java 与 C 版本

php - 像 CONCAT 与 REGEXP mysql