我们希望将性能关键型应用程序迁移到.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个问题:
我是否正在查看2个程序的正确反汇编或误导我的工具?
如果生成的指令数量的差异不是造成差异的原因是什么?
除了将所有性能关键代码保存在本机DLL中之外,我们还能做些什么。
提前致谢 史蒂夫
PS我最近收到了一个MS / Intel联合研讨会的邀请,题为“建立性能关键原生应用程序”嗯......
答案 0 :(得分:18)
我相信你在这段代码中的主要问题是检查你的数组。
如果切换到在C#中使用不安全的代码,并使用指针数学,则应该能够实现相同(或可能更快)的代码。
答案 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库集成。
答案 6 :(得分:0)
我确信C的优化与C#不同。此外,你必须期望至少一点点的性能减慢。 .NET使用框架为应用程序添加了另一层。
权衡是更快速的发展,庞大的库和功能,(应该是)少量的速度。