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

时间:2009-06-29 19:29:01

标签: c# .net 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;
}

如果我们在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

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

使用优化的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 

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

真的有3个问题:

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

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

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

  4. 提前致谢 史蒂夫

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

7 个答案:

答案 0 :(得分:18)

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

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

同样的问题是previously discussed in detail in this question

答案 1 :(得分:13)

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

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

答案 2 :(得分:6)

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

来改善性能
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秒。

答案 3 :(得分:4)

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

                    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中的抖动进行了一些改进。

答案 4 :(得分:2)

C#正在进行边界检查

在C#中运行计算部分时,不安全的代码是否与本机实现一样好?

答案 5 :(得分:1)

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

但是,如果您的应用程序在X语言中已经正常工作,我建议您不要用Y语言重写它。

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

A longer answer on a possibly related theme.

答案 6 :(得分:0)

我确信C的优化与C#不同。此外,你必须期望至少一点点的性能减慢。 .NET使用框架为应用程序添加了另一层。

权衡是更快速的发展,庞大的库和功能,(应该是)少量的速度。