为什么C#(相当慢)和Win32/C之间的性能差异?

Ste*_*eve 16 .net c c# performance winapi

我们希望将性能关键型应用程序迁移到.Net,并发现c#版本比Win32/C慢30%到100%,具体取决于处理器(移动T7200处理器上的差异更明显).我有一个非常简单的代码示例来演示这一点.为简洁起见,我将只显示C版本 - c#是直接翻译:

#include "stdafx.h"
#include "Windows.h"

int array1[100000];
int array2[100000];

int Test();

int main(int argc, char* argv[])
{
    int res = Test();

    return 0;
}

int Test()
{
    int calc,i,k;
    calc = 0;

    for (i = 0; i < 50000; i++) array1[i] = i + 2;

    for (i = 0; i < 50000; i++) array2[i] = 2 * i - 2;

    for (i = 0; i < 50000; i++)
    {
        for (k = 0; k < 50000; k++)
        {
            if (array1[i] == array2[k]) calc = calc - array2[i] + array1[k];
            else calc = calc + array1[i] - array2[k];
        } 
    }
    return calc;
}
Run Code Online (Sandbox Code Playgroud)

如果我们在Win32中查看'else'的反汇编,我们有:

35:               else calc = calc + array1[i] - array2[k]; 
004011A0   jmp         Test+0FCh (004011bc)
004011A2   mov         eax,dword ptr [ebp-8]
004011A5   mov         ecx,dword ptr [ebp-4]
004011A8   add         ecx,dword ptr [eax*4+48DA70h]
004011AF   mov         edx,dword ptr [ebp-0Ch]
004011B2   sub         ecx,dword ptr [edx*4+42BFF0h]
004011B9   mov         dword ptr [ebp-4],ecx
Run Code Online (Sandbox Code Playgroud)

(这是调试,但请耐心等待)

使用优化的exe上的CLR调试器对优化的c#版本进行反汇编:

                    else calc = calc + pev_tmp[i] - gat_tmp[k];
000000a7  mov         eax,dword ptr [ebp-4] 
000000aa  mov         edx,dword ptr [ebp-8] 
000000ad  mov         ecx,dword ptr [ebp-10h] 
000000b0  mov         ecx,dword ptr [ecx] 
000000b2  cmp         edx,dword ptr [ecx+4] 
000000b5  jb          000000BC 
000000b7  call        792BC16C 
000000bc  add         eax,dword ptr [ecx+edx*4+8]
000000c0  mov         edx,dword ptr [ebp-0Ch] 
000000c3  mov         ecx,dword ptr [ebp-14h] 
000000c6  mov         ecx,dword ptr [ecx] 
000000c8  cmp         edx,dword ptr [ecx+4]
000000cb  jb          000000D2 
000000cd  call        792BC16C 
000000d2  sub         eax,dword ptr [ecx+edx*4+8] 
000000d6  mov         dword ptr [ebp-4],eax 
Run Code Online (Sandbox Code Playgroud)

更多的说明,大概是性能差异的原因.

真的有3个问题:

  1. 我是否正在查看2个程序的正确拆卸或误导我的工具?

  2. 如果生成的指令数量的差异不是造成差异的原因是什么?

  3. 除了将所有性能关键代码保存在本机DLL中之外,我们还能做些什么.

先谢谢史蒂夫

PS我最近收到了一个MS/Intel联合研讨会的邀请,题为"建立性能关键的原生应用程序"嗯...

Ree*_*sey 18

我相信你在这段代码中的主要问题是检查你的数组.

如果你切换到在C#中使用不安全的代码,并使用指针数学,你应该能够实现相同(或可能更快)的代码.

此问题先前已在此问题中详细讨论过.


Mic*_*ael 13

我相信你看到了阵列边界检查的结果.您可以使用不安全的代码来避免边界检查.

我相信JITer可以识别类似for循环的模式,这些循环可以达到array.Length并避免边界检查,但它看起来不像你的代码可以利用它.

  • 我看到很多这些苹果 - 橘子"相同的代码"尝试与玩具代码进行性能比较.然而,我从未看到与质量相当的完整,产品质量代码的负面比较.也许是因为c#实际上并不慢. (9认同)
  • @Greg,Reed - 我在托管代码性能方面看到的大多数问题都不是像这样的CPU时间,而是加载时间和内存占用等问题.对于这些,C++仍然有一个巨大的优势(虽然糟糕的程序员可以轻易否定这个优势:) (2认同)

Jon*_*eet 6

正如其他人所说,其中一个方面是边界检查.在阵列访问方面,代码中也存在一些冗余.通过将内部块更改为:我已经设法改善了性能:

int tmp1 = array1[i];
int tmp2 = array2[k];
if (tmp1 == tmp2)
{
    calc = calc - array2[i] + array1[k];
}
else
{
    calc = calc + tmp1 - tmp2;
}
Run Code Online (Sandbox Code Playgroud)

这一变化将总时间从约8.8秒降至约5秒.