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

Why the performance difference between C# (quite a bit slower) and Win32/C?

我们希望将性能关键应用程序迁移到.NET,并发现C版本比Win32/C慢30%到100%,这取决于处理器(差异更多地标记在移动T7200处理器上)。我有一个非常简单的代码示例来演示这一点。为了简洁起见,我将展示C版——C是直接翻译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#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;
}

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

1
2
3
4
5
6
7
8
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

(这是调试中的,但请放心)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
                    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

更多的指令,大概是性能差异的原因。

所以有三个问题:

  • 我是在看两个程序的正确反汇编,还是工具误导了我?

  • 如果生成的指令数量的差异不是导致差异的原因,那是什么?

  • 除了将所有性能关键的代码保存在本机dll中之外,我们还可以做些什么呢?

  • 提前谢谢史蒂夫

    PS我最近收到了一个邀请,参加一个题为"构建性能关键型本机应用程序"的MS/Intel联合研讨会。


    我相信这段代码中的主要问题是对数组进行边界检查。

    如果您切换到使用C中的不安全代码,并使用指针数学,您应该能够获得相同(或可能更快)的代码。

    在这个问题中,以前曾详细讨论过这个问题。


    我相信您看到的是数组边界检查的结果。您可以使用不安全的代码来避免边界检查。

    我相信Jiter可以识别类似于数组长度的循环的模式,并避免边界检查,但看起来您的代码不能利用这一点。


    正如其他人所说,其中一个方面是边界检查。在数组访问方面,代码中还存在一些冗余。通过将内部块更改为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int tmp1 = array1[i];
    int tmp2 = array2[k];
    if (tmp1 == tmp2)
    {
        calc = calc - array2[i] + array1[k];
    }
    else
    {
        calc = calc + tmp1 - tmp2;
    }

    这一变化使总时间从~8.8秒降至~5秒。


    为了好玩,我尝试在Visual Studio 2010的C中构建它,并查看了Jitted反汇编:

    1
    2
    3
    4
    5
    6
                        else
                            calc = calc + array1[i] - array2[k];
    000000cf  mov         eax,dword ptr [ebp-10h]
    000000d2  add         eax,dword ptr [ebp-14h]
    000000d5  sub         eax,edx
    000000d7  mov         dword ptr [ebp-10h],eax

    他们对clr的4.0中的抖动做了一些改进。


    C正在进行边界检查

    在C不安全代码中运行计算部分时,它的性能和本机实现一样好吗?


    如果您的应用程序的性能关键路径完全由未检查的数组处理组成,我建议您不要用C_重写它。

    但是,如果您的应用程序在语言X中已经运行良好,我建议您不要用语言Y重写它。

    你想从重写中获得什么?至少,要认真考虑混合语言的解决方案,使用已经调试过的C代码作为高性能部分,并使用C获得良好的用户界面或方便地与最新的富.NET库集成。

    关于可能相关主题的较长回答。


    我确信C的优化与C不同。另外,您必须期望至少有一点性能降低。.NET为带有框架的应用程序添加了另一层。

    取舍是更迅速的发展,庞大的图书馆和功能,为(什么应该是)少量的速度。