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

3
我和我的朋友编写了一个加密模块,我们希望将它移植到多种语言中,以便进行平台无关的加密。最初是用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++代码,它并不是非常复杂。没有任何包含文件,只有“基本”数学运算和我们称之为“状态跳转”的操作来产生伪随机结果。我们做的最复杂的事情是位运算,实际上进行加密和未经检查的乘法在初始哈希阶段。有动态分配的二维数组,它们在Encryption对象的生命周期内保持活动状态(并在析构函数中正确释放)。这里只有180行代码。好吧,我的微观优化可能不是必要的,但我应该相信它们不是问题,而是时间问题。为了真正强调这一点,这是程序中最复杂的代码行:
input[L + offset] ^= state[state[SIndex ^ 255] & 63];
我没有移动数组或处理对象。
从语法上讲,整个代码集都运行得很完美,如果我用C#加密并用C++或Java解密,或者用任意一种语言加密并用另外两种语言解密,所有3种语言都会像你期望的那样互相交互。
我并不一定希望C++比C#或Java运行得更快(它们之间的差距不到1 MB/s),但我相信有办法让C++运行得与它们一样快,或至少比现在更快。我承认我不是C++专家,我肯定没有像你们中的许多人那样经验丰富,但如果我可以将99%的代码从C#复制并粘贴到C++中,并在5分钟内使其正常工作,那么我有点失望它需要两倍的执行时间。
重新编辑:
我发现了一种在Visual Studio中优化的方法,我之前忘记设置了。现在C++运行的速度比C#快50%。感谢所有的提示,我在研究中学到了很多关于编译器的知识。

3
我对此感到惊讶,因为C++应该比它们两个都要快,但如果没有看到代码,很难判断。确保尽可能少地使用框架。 - Xetius
4
你确定你正在使用发布版本的配置来运行吗? - SadSido
24
你已经进行的那些优化是微观优化,不应该执行:1)它们会降低代码的可读性。2)编译器会自动执行它们,并且它可能比你更好。 - Georg Schölly
6
我看了一下你个人资料中网站上的一些源代码,说实话,你的C++水平相当糟糕。如果你不想发布源代码(就像你在“大型项目”部分所示),那没关系,但是可以在stackoverflow上寻找一些好的C++书籍列表。你还有很多需要学习的地方... - Pieter
3
为了得到帮助,您需要发布代码,即使只是“核心”部分。虽然在这种情况下 C# 可能更快,但我怀疑像加密这样数学上的事情在 C++ 中不应该更慢。正如其他人所说,很可能您没有按照 C++ 的方式进行操作。此外,请删除您的微观优化,让编译器为您执行。同样(我并不是要冒犯),因为您试图手动优化,我认为您没有充分利用 C++。 - GManNickG
显示剩余3条评论
20个回答

24
没有源代码很难评估你的加密算法/程序的性能。不过我猜想在将其移植到C++时,您可能会犯一个“错误”,这意味着您使用了一种低效的方式(例如,会发生大量对象复制)。也许您还使用了VC 6,而VC 9可以生成更好的代码。
至于“x >> 3”优化……现代编译器会自动将整数除法转换为位移操作。不用说,这种优化可能根本不是您程序的瓶颈。您应该首先对其进行分析以找出您花费时间最多的地方 :)

1
实际上,现代编译器通常不会这样做,因为我们已经将足够的晶体管投入到算术逻辑单元中,以便直接执行数学运算更快。 - Ana Betts
@Paul:你说得对,他们确实不经常这样做。我只是想指出,没有必要自己去做,因为编译器会在适当的时候完成它。 如果你有更详细的信息,请告诉我,我很乐意学到新东西 :) - Christian
+1 建议进行性能分析。非常有趣的是,看看 C# 实现的分析结果是否像 C++ 实现一样在函数中花费了同样多的时间。 - James Bedford

15

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

你正在进行微观优化,但需要检查你解决方案的整体设计是否在C ++中合理。 重新设计解决方案的大部分,使其在C ++中更好地运行可能是一个好主意。

与所有性能相关的事情一样,首先对代码进行分析,然后修改,再次进行分析。 反复操作直到达到可接受的性能水平。


13

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

你可以写出比C#更快的代码,但你也可以写出更慢的代码。特别是调试版本,在C++中可能极其缓慢。所以要看看你的编译器使用了哪种优化方式。

大多数情况下,当移植应用程序时,C#程序员倾向于使用“创建百万个新对象”的方法,这确实会导致C++程序缓慢。你需要重写这些算法,使用预分配的数组,并通过紧密循环运行这些数组。

使用预分配内存,利用C++使用指向正确pod结构数据的指针的优点。

但这真的取决于你在你的代码中写了什么。

所以,测量你的代码并查看哪些实现烧掉了最多的CPU,然后设计你的代码以使用正确的算法。


7
你的计时结果肯定不是我所期望的,即使是用良好编写的C++和C#。你几乎肯定在编写低效的C++代码(除非你没有使用相同的编译选项进行测试,确保你正在测试发布版本,并检查优化选项)。
然而,像你提到的微小优化对于改善性能实际上没有什么作用。你浪费时间做一些编译器会自动完成的事情。
通常你会从算法入手,但在这种情况下,我们知道算法并不是导致性能问题的原因。我建议使用性能分析器来查看是否可以找到大量时间浪费的地方,但它可能不会发现与C#或Java不同的任何内容。
我建议看看C++与Java和C#有何不同。一个重要的区别是对象。在Java和C#中,对象以与C++指向对象的指针相同的方式表示,尽管从语法上看不出来。
如果你在Java和C++中移动对象,则在Java中移动指针很快,而在C++中移动对象可能很慢。查找你使用中等或大型对象的位置。你将它们放在容器类中吗?这些类会移动对象。将它们更改为指针(最好是智能指针,如std::tr1::shared_ptr<>)。
如果你不熟悉C++(有经验和能力的C++程序员几乎不太可能进行微观优化),请尽量找到一个熟悉C++的人。C++不是一种非常简单的语言,比Java或C#具有更多的遗留负担,你可能会错过很多东西。


7
将性能关键代码从一种语言移植到另一种语言通常不是一个好主意。你往往无法充分利用目标语言(在这种情况下是C++)的优势。
我见过的最糟糕的C++代码之一是从Java移植过来的。几乎每个地方都使用了"new" - 这对Java来说很正常,但对C++来说肯定会严重影响性能。
通常最好不要移植,而是重新实现关键部分。

5
是的。不知何故,当人们比较两种语言的性能时,结果总是他们最初编写代码所用的语言更快。这并不奇怪,只是意味着您的测试存在缺陷,但这从未阻止人们进行这些比较。 - jalf

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)。如果对象的生命周期超出了当前方法/函数的范围,则我们使用new,但我们始终将返回的变量包装在智能指针中(以便它将被正确删除)。

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

    Encrypt(m);
}

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

    EncryptUsingPointer(m);
}

我认为平均而言,C#开发人员对于垃圾回收和内存管理的细节比你所暗示的更加了解,特别是考虑到像IDisposable这样的接口的包含,它允许开发人员确保内存密集型对象在超出范围时被清理。此外,C#允许开发人员通过引用传递参数以进一步最小化内存使用。然而,我承认他们通常不像您平均水平的C++开发人员那样了解,但我不会说他们完全无知。 - Maurice Reeves
@MauriceReeves:C#和using子句(带IDisposable)是对Java的巨大补充。但是,除非C#程序员不得不编写一个企业级大小的服务器应用程序,其中默认的内存管理方案完全无用(然后深入语言的内部进行修改以使事情正常工作(我应该说是高效,但在我看来,语言需要高效才能正确))(我的团队在为MS构建“移动设备”广告平台的服务器基础架构时做到了这一点)。 - Martin York
我同意这一点。我有几个非常小范围的应用程序,是用C#编写的。由于它们需要更多的处理和内存资源,我正在将它们转换为C++。我认为我已经接近C#的可行极限了。 - Maurice Reeves

4
展示你的代码。如果我们不知道你的代码长什么样,我们就无法告诉你如何优化它。
将除以常数的操作转换为移位操作是完全浪费时间的。即使是最愚蠢的编译器也能进行这种类型的变换。
你可以通过需要编译器无法获得的信息来实现性能优化。编译器知道除以二的幂等同于右移操作。
此外,很少有理由期望C++更快。C++更加依赖于你编写好的代码。C#和Java几乎无论你做什么都会生成相当高效的代码。但是在C++中,只要犯一两个错误就会导致性能下降。
老实说,如果你因为C++是“本地”或“更接近底层”而期望它更快,那么你已经落后了十年。JIT语言可以非常高效,并且除了一两个例外之外,它们没有理由比本地语言慢。

您可能会发现这些帖子有启发性。简而言之,它们表明,是的,C++具有更快的潜力,但在大多数情况下,除非您极力优化代码,否则C#将与其一样快或更快。

如果您想让C++代码与C#版本竞争,则有几个建议:

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

1
没有任何理由认为C++版本会慢很多。 - David Thornley
当然。这取决于代码,但编写非常慢的C++代码非常容易。例如,过度依赖动态内存分配可以轻松使您的C++代码比等效的C#慢一个数量级。当然,C++没有必要慢那么多,但很容易发生这种情况。 - jalf

2

完全偏题,但是...

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

(引用)

由我的朋友Karl Wessels和我组合而成,我们认为我们拥有一个非常强大的新算法。

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

[...]

我们还在考虑获得版权并最终商业发布。

我不想打击你,但是正确实现加密很难。非常难。

我不是说一个20岁的网站开发者无法开发出优于所有现有算法的加密算法,但这极其不可能,我非常怀疑,我认为大多数人都会这样想。

关心加密的人不会使用未公开的算法。我不是说你必须公开源代码,但是如果你想被认真对待,算法的工作原理必须是公开的,并经过审查...


3
计划最终发布源代码并允许检验。在我们经过加密强度的适当测试之前,我们不愿意过早这样做。不久我们将采取这些步骤。附言:我不想吹嘘自己,但我赢得了国家级Java和C ++编程比赛,今年春天我将参加国际比赛。尽管我知道加密是一件非常复杂的事情,但我试图保持自信而不傲慢。 - Corey Ogburn

1
在另一个帖子中,我指出直接从一种语言翻译到另一种语言几乎总会导致新语言版本的运行效果更差。
不同的语言采用不同的技术。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接