C#比C ++运行得更快?

时间:2009-09-14 13:55:42

标签: c# c++ optimization

我和朋友已经编写了一个加密模块,我们希望将其移植到多种语言,这样它就不是特定于平台的加密。最初是用C#编写的,我把它移植到C ++和Java中。 C#和Java都将以大约40 MB / s的速度加密,但C ++只能以大约20 MB / s的速度加密。为什么C ++运行速度要慢得多?是因为我使用的是Visual C ++吗?

我可以做些什么来加速我的代码?是否有更好的编译器可以更好地优化C ++?

我已经尝试过优化代码本身,例如使用x >> 3而不是x / 8(整数除法),或y & 63而不是y % 64和其他技术。如何以不同方式构建项目以使其在C ++中更具性能?

编辑:

我必须承认我没有研究过编译器如何优化代码。我将在学院上课,致力于学习编译器和口译员。

至于我在C ++中的代码,它并不是很复杂。有NO包括,有“基本”数学以及我们称之为“状态跳跃”以产生伪随机结果的东西。我们所做的最复杂的事情是在初始散列阶段实际执行加密和未经检查的乘法的按位运算。有动态分配的2D数组,它们在Encryption对象的生命周期内保持活跃状态​​(并在析构函数中正确释放)。这里只有180行。好吧,所以我的微观优化不是必要的,但我应该相信它们不是问题,而是时间问题。要真正深入研究,这是程序中最复杂的代码行:

输入[L +偏移] ^ =状态[状态[SIndex ^ 255]& 63];

我没有移动数组或使用对象。

从语法上讲,整个代码集运行完美,如果我用C#加密某些东西并用C ++或Java解密它,它将无缝地工作,所有3种语言都会按照你的预期进行交互。

我不一定希望C ++比C#或Java(相互之间的速度在1 MB / s以内)运行得更快,但我确信有一种方法可以让C ++以同样快的速度运行,或者至少更快那就是现在。我承认我不是C ++专家,我肯定不像你们许多人那样经验丰富,但如果我能将99%的代码从C#剪切并粘贴到C ++并让它在5中工作分钟,然后我有点说出执行需要两倍的时间。

RE-编辑: 我在Visual Studio中发现了一个我忘记设置的优化。现在C ++的运行速度比C#快50%。感谢所有提示,我在研究中学到了很多关于编译器的知识。

20 个答案:

答案 0 :(得分:24)

如果没有源代码,很难说出加密算法/程序的性能。 我认为虽然你把它移植到C ++时犯了一个“错误”,这意味着你以低效的方式使用它(例如,大量的对象复制发生)。也许你也使用过VC 6,而VC 9会产生更好的代码。

对于“x>> 3”优化...现代编译器会将整数除法自身转换为位移。不用说,这种优化可能根本不是你的程序的瓶颈。您应首先对其进行分析,以找出您花费大部分时间的地方:)

答案 1 :(得分:15)

这个问题非常广泛。在C#中有效的东西在C ++中效率可能不高,反之亦然。

您正在进行微观优化,但您需要检查解决方案的整体设计,以确保它在C ++中有意义。重新设计解决方案的大部分内容以使其在C ++中更好地工作可能是个好主意。

与性能相关的所有内容一样,首先分析代码,然后修改,然后再次分析。重复,直到达到可接受的性能水平。

答案 2 :(得分:13)

C#中“相对”快速的东西在C ++中可能会非常慢。

您可以在C ++中编写“更快”的代码,但您也可以编写更慢的代码。特别是在C ++中,调试版本可能非常慢。所以请看一下编译器的优化类型。

主要是在移植应用程序时,C#程序员倾向于使用“创建一百万个新对象”的方法,这确实使C ++程序变慢。您可以重写这些算法以使用预先分配的数组,并使用紧密循环运行这些算法。

使用预先分配的内存,您可以利用C ++的优势,通过将这些内容转换为正确的pod结构化数据来使用内存指针。

但这实际上取决于你在代码中写的内容。

所以测量你的代码,看看实现烧掉最多cpu的位置,然后构造你的代码以使用正确的算法。

答案 3 :(得分:7)

你的计时结果绝对不是我所期望的用精心编写的C ++和编写良好的C#。你几乎肯定会编写效率低下的C ++。 (或者,或者你没有使用相同的选项进行编译。确保你正在测试发布版本,并检查优化选项。

然而,像你提到的那样,微优化将不会有效地改善性能。你在浪费时间做编译器会为你做的事情。

通常从查看算法开始,但在这种情况下,我们知道算法不会导致性能问题。我建议使用分析器来查看是否可以找到一个大时间接收器,但它可能找不到与C#或Java不同的东西。

我建议看一下C ++与Java和C#的区别。对象有一件大事。在Java和C#中,对象的表示方式与C ++指向对象的方式相同,但从语法中来看并不明显。

如果您正在使用Java和C ++移动对象,那么您在Java中移动指针很快,而C ++中的对象可能很慢。寻找使用中型或大型物体的位置。你把它们放在容器类中吗?这些类移动对象。将它们更改为指针(最好是智能指针,如std :: tr1 :: shared_ptr<>)。

如果你没有C ++经验(并且经验丰富且称职的C ++程序员不太可能进行微优化),那么试着找一个人。 C ++不是一种非常简单的语言,拥有比Java或C#更多的传统包袱,你可能会遗漏很多东西。

答案 4 :(得分:7)

免费的C ++个人资料:

What's the best free C++ profiler for Windows?

答案 5 :(得分:7)

将性能关键代码从一种语言“移植”到另一种语言通常是一个坏主意。你倾向于不使用目标语言(在这种情况下是C ++)充分发挥潜力。

我见过的一些最糟糕的C ++代码是从Java移植的。几乎所有东西都有“新” - 对Java来说是正常的,但对于C ++来说肯定是性能杀手。

通常最好不要移植,但重新实现关键部分。

答案 6 :(得分:5)

C#/ Java程序转换不好的主要原因(假设其他一切都是正确的)。 C#/ Java开发人员是不是正确地理解了对象和引用的概念。注意在C#/ Java中,所有对象都通过(相当于)指针传递。

Class Message
{
    char buffer[10000];
}

Message Encrypt(Message message)  // Here you are making a copy of message
{
    for(int loop =0;loop < 10000;++loop)
    {
        plop(message.buffer[loop]);
    }

    return message;  // Here you are making another copy of message
}

要以(更多)C ++样式重写它,您应该使用引用:

Message& Encrypt(Message& message)  // pass a reference to the message
{
   ...

    return message;  // return the same reference.
}

C#/ Java程序员遇到困难的第二件事是缺少垃圾收集。如果你没有正确释放任何内存,你可能会开始内存不足而且C ++版本正在颠簸。在C ++中,我们通常在堆栈上分配对象(即没有新的)。如果对象的生命周期超出了方法/函数的当前范围,那么我们使用new,但是我们总是将返回的变量包装在智能指针中(以便正确删除它)。

void myFunc()
{
    Message    m;
    // read message into m

    Encrypt(m);
}

void alternative()
{
    boost::shared_pointer<Message>  m(new Message);

    EncryptUsingPointer(m);
}

答案 7 :(得分:4)

显示您的代码。如果我们不知道它的外观,我们无法告诉您如何优化代码。

你绝对浪费时间将常数转换为常移操作。即使是最愚蠢的编译器也可以进行那种脑死亡转换。

您可以获得性能的优化是需要编译器 所拥有的信息的优化。编译器知道这个除以2的幂等于右移。

除此之外,没有理由期望 C ++更快。 C ++更依赖于编写好的代码。无论你做什么,C#和Java都会产生非常高效的代码。但是在C ++中,只有一两个失误会削弱性能。

老实说,如果你期望C ++更快,因为它是“原生的”或“更接近金属”,那么你已经晚了十年。 JIT的语言非常有效,除了一两个例外,没有理由说它们必须比母语慢。

您可能会发现these posts具有启发性。 简而言之,它们表明,是的,最终,C ++的潜力更快,但在大多数情况下,除非你采用极端优化代码,否则C#将同样快,或者更快。

如果您希望C ++代码与C#版本竞争,那么请提出一些建议:

  • 启用优化(您希望已经完成此操作)
  • 仔细考虑如何进行磁盘I / O(IOStremas不是一个理想的库)
  • 描述您的代码以了解需要优化的内容。
  • 了解您的代码。研究汇编程序输出,看看可以更有效地完成哪些工作。
  • C ++中的许多常见操作都非常慢。动态内存分配是一个很好的例子。它在C#或Java中几乎是免费的,但在C ++中却非常昂贵。堆栈分配是你的朋友。
  • 了解代码的缓存行为。您的数据是否散布在各处?因此,您的代码效率低下应该不足为奇。

答案 8 :(得分:2)

完全是话题但......

我在您的个人资料http://www.coreyogburn.com/bigproject.html链接到的主页上找到了有关加密模块的一些信息

(报价)

  

由我和我的好友Karl Wessels和我合作,我们相信我们有一个非常强大的新算法。

     

我们的加密与许多现有加密的区别在于我们的加密既快又安全。目前,加密100 MB需要5秒钟。据估计解密它需要4.25 * 10 ^ 143年!

     

[...]

     

我们也在考虑获得版权和最终的商业版本。

我不想劝阻你,但加密正确很难。很难。

我并不是说一个二十年前的网络开发人员不可能开发出比所有现有算法更优越的加密算法,但这种可能性极小,而且我很怀疑,我认为大多数人都会这样做。

没有人关心加密会使用未发布的算法。我不是说你必须打开你的源代码,但如果你想要认真对待,那么算法的运作必须是公开的,并仔细审查......

答案 9 :(得分:1)

如果对表现很认真,你可能想要认真分析。

另外,C#Java和C ++中使用的“字符串”对象实现在C ++中明显变慢。

答案 10 :(得分:1)

在Dobbs Journal博士中有一篇名为Microbenchmarking C++, C#, and Java的文章,在那里你可以看到一些实际的基准,你会发现C#有时比C ++更快。其中一个更极端的例子是single hash map benchmark。 .NET 1.1在126处明显胜出,VC ++远远落后于537。

如果你声称像C#这样的语言比C ++更快,有些人会不会相信你,但它实际上可以。但是,使用分析器和C ++提供的非常高级别的细粒度控件应该可以让您重写应用程序,使其具有高性能。

答案 11 :(得分:1)

在某些领域,VM上运行的语言优于C / C ++,例如新对象的堆分配。您可以找到更多详细信息here

答案 12 :(得分:1)

在某些情况下,基于VM的语言(如C#或Java)可能比C ++版本更快。至少如果你没有在优化方面投入太多工作并且对背景中发生的事情有很好的了解。一个原因是VM可以在运行时优化字节代码并找出程序的哪些部分经常使用并改变其优化策略。另一方面,老式编译器必须决定如何在编译时优化程序,并且可能找不到最佳解决方案。

答案 13 :(得分:1)

C#JIT可能在运行时注意到CPU能够运行一些高级指令,并且正在编译成比编译C ++更好的东西。

你可能(当然有足够的努力)通过使用指定CPU可用的最复杂的指令进行编译并使用算法知识告诉编译器在特定阶段使用SIMD指令来表现优异。

但在对代码进行任何花哨的更改之前,请确保C ++编译为C.P.U,而不是更原始的(Pentium?)。

编辑:

如果你的C ++程序做了很多不明智的分配和解除分配,这也可以解释它。

答案 14 :(得分:1)

在另一个帖子中,我指出从一种语言到另一种语言的直接翻译几乎总是会在新语言的版本中运行得更差。

不同的语言采用不同的技术。

答案 15 :(得分:0)

尝试使用intel编译器。 VC或gcc要好得多。至于原来的问题,我会持怀疑态度。尽量避免使用任何容器,并尽量减少违规函数中的内存分配。

答案 16 :(得分:0)

[笑话]第13行[/ Joke]

中有错误

现在,严肃地说,没有源代码,没有人可以回答这个问题。

但是作为一个经验法则,C ++的速度比管理速度慢很多,这很可能表明了内存管理和对象所有权问题的不同。

例如,如果您的算法在处理循环内进行任何动态内存分配,则会影响性能。如果按值传递重结构,则会影响性能。如果您执行不必要的对象副本,则会影响性能。滥用行为会导致性能下降。还在数着。

我知道被遗忘的案件“&amp;”在参数名称导致数周的分析/调试之后:

void DoSomething(const HeavyStructure param);  // Heavy structure will be copied
void DoSomething(const HeavyStructure& param); // No copy here

因此,请检查您的代码以找出可能的瓶颈。

答案 17 :(得分:0)

C ++不是必须使用类的语言。在我看来,使用OOP方法并不合理,因为它并没有真正帮助。对于加密/解密器,最好不要使用类;使用数组,指针,尽可能少地使用函数/类/文件。最佳加密系统由包含少量功能的单个文件组成。在您的函数运行良好之后,如果您愿意,可以将其包装到类中。还要检查发布版本。存在巨大的速度差异

答案 18 :(得分:0)

没有什么比好的机器/汇编代码更快,所以我编写C / C ++时的目标是编写我的代码,使编译器理解我生成良好机器代码的意图。内联是我最喜欢的方式。

首先,这是一个旁边。好的机器代码:

  • 比内存更频繁地使用寄存器
  • 很少分支(if / else,for和while)
  • 比函数调用更频繁地使用内存
  • 很少动态分配(来自堆)的内存(

如果你有一个只有很少代码的小类,那么在类定义的主体中实现它的方法,并在你使用它时在本地(在堆栈上)声明它。如果类足够简单,那么编译器通常只会生成一些指令来影响它的行为,而不需要任何函数调用或内存分配来减慢速度,就像你编写了所有冗长和非面向对象的代码一样。我通常打开程序集输出(/ FAs / Fa with Visual C ++),所以我可以检查输出。

很高兴有一种语言可以让你编写高级的,封装的面向对象的代码,并且仍然可以转换成简单,纯粹,闪电般快速的机器代码。

答案 19 :(得分:0)

这是我的2美分。

我在C(和C#)中写了一个BlowFish密码。 C#与C几乎“完全相同”。

我如何编辑(我现在无法记住数字,所以只是召回比率):

C native:       50
C managed:      15
C#:             10

如您所见,本机编译输出执行任何托管版本。为什么?

我不是100%肯定,但是我的C版本编译为非常优化的汇编代码,汇编程序输出几乎与我发现的手写汇编程序相同。