【问题标题】:Why the performance difference between C# (quite a bit slower) and Win32/C?为什么 C#(相当慢)和 Win32/C 之间的性能差异?
【发布时间】:2009-06-29 19:29:00
【问题描述】:

我们正在寻求将性能关键应用程序迁移到 .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 中之外,我们还能做些什么。

提前致谢 史蒂夫

PS 我最近确实收到了参加 MS/Intel 联合研讨会的邀请,主题是“构建性能关键的原生应用程序”嗯...

【问题讨论】:

  • 你能去掉汇编指令之间的所有换行符吗?
  • 与往常一样,对其进行分析,以准确了解对性能影响最大的成本。 (我们无法看到您的代码中花费了哪些时间,所以问我们没有意义。请改为询问分析器)除此之外,一个简单的技巧可能是通过 NGen 运行您的 C# 代码。这应该会大大提高性能。
  • 您要比较的 CLR 版本。据我所知,.NET 3.5 SP1 JIT 编译器比旧的更高效。 x64 JIT 优化器也比 x86 更激进。
  • 顺便说一句,“直接”C# 翻译很重要。您确定要检查启用优化的 JIT 生成的程序集吗?
  • 另请参阅此相关问题:stackoverflow.com/questions/883642/…

标签: c# .net c performance winapi


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 我看到很多这样的苹果-橙子“相同代码”尝试与玩具代码进行性能比较。然而,我从来没有看到与质量相当的完整产品质量代码的负面比较。也许是因为 c# 实际上并不慢。
    • @Greg D:我同意。我几乎专注于高性能、面向科学的数值处理。 C# 确实有一个非常不同的性能。不过,profile 比 C++ 好,所以分析很关键 - 但总的来说,通过正确的分析和代码调整,您可以让 C# 与 C++ 一样快。
    • @Greg, Reed - 我看到的托管代码性能的大多数问题都不是像这样的 CPU 时间,而是加载时间和内存占用等问题。对于这些,C++ 仍然具有巨大的优势(尽管糟糕的程序员很容易否定这种优势:)
    • @Michael:是的。尤其是在受管理的世界中,启动时间往往会受到影响。 32 位的内存限制是另一个问题,托管并不总是符合本机(托管通常上限为 1.2-1.4gb/进程,尽管在大多数情况下压缩 GC 可以弥补这一点)。
    【解决方案3】:

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

    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 秒。

    【讨论】:

    • @Jon:也许我遗漏了一些东西,但我无法衡量您的版本和 OP 版本之间的任何显着性能差异。事实上,我也不认为如此微小的变化会对性能产生如此大的影响。
    • 我也不会特别喜欢,但它确实对我有用,在 .NET 3.5 和 4.0b1 上。在 32 位 Vista 上使用 /o+ /debug- 作为控制台应用程序编译。我还更改了 i 和 k 变量的范围,但我怀疑这很重要。
    • (我已经测试了足够多的时间以确保它不仅仅是侥幸,顺便说一句:)
    • @Jon:“我也改变了 i 和 k 变量的范围,但我怀疑这很重要。”我检查了这一点,似乎 i 和 k 的有限范围实际上是性能改进的原因。如果 i 和 k 只是 for 循环的本地,则优化器可能能够删除边界检查,因为它可以确定 i 和 k 始终在数组的边界内(我在 XP/.NET 3.5 上检查了这一点)。
    • 这不是 只是 - 当我第一次更改范围时,它没有任何区别 - 做出答案中指定的更改然后产生了巨大的差异。我想这是两件事的结合。
    【解决方案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 中对抖动进行了多项改进。

    【讨论】:

      【解决方案5】:

      C# 正在做边界检查

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

      【讨论】:

        【解决方案6】:

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

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

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

        A longer answer on a possibly related theme.

        【讨论】:

          【解决方案7】:

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

          权衡是更快速的开发、庞大的库和函数,而(应该是)少量的速度。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-02-12
            • 2019-06-03
            • 2018-03-17
            • 1970-01-01
            • 2013-07-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多