【问题标题】:C# running faster than C++?C# 比 C++ 运行得更快?
【发布时间】:2025-12-09 22:55:02
【问题描述】:

我和一个朋友编写了一个加密模块,我们希望将它移植到多种语言,这样它就不是特定于平台的加密。最初用 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++ 代码,它不是很复杂。没有包含,有“基本”数学以及我们称之为“状态跳跃”的东西来产生伪随机结果。我们所做的最复杂的事情是在初始散列阶段实际执行加密和未经检查的乘法的按位运算。有动态分配的 2D 数组在 Encryption 对象的生命周期内保持活动状态(并在析构函数中正确释放)。这里只有 180 行。好的,所以我的微优化不是必需的,但我应该相信它们不是问题,是时候了。为了真正深入了解,这是程序中最复杂的代码行:

输入[L + offset] ^= state[state[SIndex ^ 255] & 63];

我不是在移动数组,也不是在处理对象。

从语法上讲,整个代码集运行完美,如果我用 C# 加密某些东西并用 C++ 或 Java 解密它,这三种语言都可以按照您的预期进行交互。

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

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

【问题讨论】:

  • 我对此感到惊讶,因为 C++ 应该比这两者都快,但是如果没有看到代码就很难分辨。确保您使用尽可能少的框架。
  • 您确定,您正在运行发布配置吗?
  • 您已经进行的那些优化是微优化,不应该进行:1)它们使代码的可读性降低。 2) 编译器为你做这些,它可能比你更好。
  • 我在您的个人资料中查看了网站上的一些源代码,坦率地说,您的 C++ 相当糟糕。如果您不想发布源代码(正如您的“大项目”部分所建议的那样),那很好,但请在 * 上查找一些关于 C++ 的好书清单。你有很多东西要学...
  • 您需要发布代码以供任何人帮助您,即使只是“核心”。虽然在这种情况下 C# 当然可能更快,但我怀疑像加密这样的数学运算在 C++ 中应该更慢。就像其他人所说的那样,您可能没有以 C++ 的方式做 C++ 的事情。另外拿出你的微优化,让编译器为你做!同样(我不是以冒犯的方式),因为你试图手动优化,我不认为您正在充分发挥 C++ 的潜力。

标签: c# c++ optimization


【解决方案1】:

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

至于“x >> 3”优化......现代编译器确实将整数除法转换为位移位。不用说,这种优化可能根本不是你程序的瓶颈。您应该首先对其进行分析,以了解您大部分时间都在哪里度过:)

【讨论】:

  • 实际上,现代编译器通常这样做,因为我们在 ALU 上投入了足够多的晶体管,因此直接进行数学运算实际上更快。
  • @Paul:你说得对,他们不经常这样做。我只是想指出没有必要自己做,因为编译器会做(当它对编译器有意义时)。如果您有关于此问题的更多详细信息,我很乐意学习新知识:)
  • +1 用于建议分析。看看 C# 实现的配置文件在函数上花费的时间是否与 C++ 实现的时间相同会很有趣。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

    您可以用 C++ 编写“更快”的代码,但也可以编写慢得多的代码。特别是调试构建在 C++ 中可能非常慢。因此,请查看编译器的优化类型。

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

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

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

    因此,请测量您的代码,看看哪些实现消耗的 CPU 最多,然后构建您的代码以使用正确的算法。

    【讨论】:

      【解决方案4】:

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

      但是,就像您提到的那样,微优化实际上并不能有效提高性能。你在浪费时间做编译器会为你做的事情。

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

      我建议看看 C++ 与 Java 和 C# 有何不同。一件大事是对象。在 Java 和 C# 中,对象的表示方式与 C++ 指向对象的指针相同,尽管从语法上看并不明显。

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

      如果您没有 C++ 方面的经验(并且经验丰富且称职的 C++ 程序员极不可能进行微优化),请尝试寻找有经验的人。 C++ 并不是一种真正简单的语言,它比 Java 或 C# 有更多的遗留包袱,而且你可能会遗漏很多东西。

      【讨论】:

        【解决方案5】:

        【讨论】:

          【解决方案6】:

          将性能关键代码从一种语言“移植”到另一种语言通常是个坏主意。您往往不会充分利用目标语言(在本例中为 C++)。

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

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

          【讨论】:

          • 是的。不知何故,当人们比较两种语言的性能时,总是发现他们最初编写代码时使用的语言更快。这不应该让任何人感到惊讶,它只是意味着您的测试存在缺陷,但这从未阻止人们进行这些比较。
          【解决方案7】:

          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);
          }
          

          【讨论】:

          • 我想说的是,一般的 C# 开发人员在垃圾收集和内存管理的细节上比你暗示的要好,特别是考虑到包含像 IDisposable 这样的接口,它允许开发人员确保内存-密集对象在超出范围时会被清理。此外,C# 允许开发人员通过 ref 传递参数,以进一步减少内存使用量。不过,我确实承认,他们通常不像普通的 C++ 开发人员那样见多识广,但我不会说他们完全无知。
          • @MauriceReeves:C# 和 using clause(带有 IDisposable)是 C# over Java 的一个很好的补充。但是在 C# 敏锐的程序员不得不编写企业级服务器应用程序之前,默认的内存管理方案是完全没用的(然后侵入语言的深层内部以使事情正常工作(我应该说效率很高,但在我看来语言需要高效才能正确))(我的团队为 MS 的“移动设备”广告平台构建了服务器基础架构)。
          • 我会同意你的。我有一些我用 C# 编写的范围非常小的应用程序,我正在转换为 C++,因为它们正在变得更加处理和内存密集型。我想我已经碰到了我在 C# 中可以完成的诡计的极限。
          【解决方案8】:

          显示您的代码。如果我们不知道代码是什么样子,我们就无法告诉您如何优化您的代码。

          将常量除法转换为移位操作绝对是在浪费时间。即使是最愚蠢的编译器也可以进行这种脑死转换。

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

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

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

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

          如果您希望您的 C++ 代码与 C# 版本竞争,那么有几点建议:

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

          【讨论】:

          • 当然也没有理由期望 C++ 版本会慢很多。
          • 当然是。取决于代码,但编写非常慢的 C++ 代码非常容易。例如,过度依赖动态内存分配很容易使您的 C++ 代码比等效的 C# 慢一个数量级。当然,没有理由 C++ 必须 这么慢,但它很容易发生。
          【解决方案9】:

          完全是主题,但是...

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

          (引用)

          由我的好友 Karl Wessels 和我共同打造,我们相信我们有一个相当强大的新算法。

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

          [...]

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

          我不想让您气馁,但是正确加密很难。很难。

          我并不是说一个 20 岁的 Web 开发人员不可能开发出超越所有现有算法的加密算法,但这是极不可能的,而且我非常怀疑,我想大多数人都会这样。

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

          【讨论】:

          • 计划最终发布源代码并允许审查。在我们正确测试加密强度之前,我们不敢过早地这样做。很快我们将采取这些步骤。 PS。我讨厌自吹自擂,但我赢得了全国比赛,今年春天我参加了 Java 和 C++ 编程国际比赛。虽然我试图保持自信而不自大,但我知道加密是一件非常复杂的事情。
          【解决方案10】:

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

          【讨论】:

            【解决方案11】:

            Doctor Dobbs Journal 上有一篇名为Microbenchmarking C++, C#, and Java 的旧文章,您可以在其中看到一些实际的基准测试,并且您会发现 C# 有时比 C++ 更快。更极端的例子之一是single hash map benchmark。 .NET 1.1 以 126 分明显胜出,VC++ 以 537 分远远落后。

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

            【讨论】:

            • 我觉得这个结果有点奇怪,而且文章似乎没有提供使用的源代码。没有它,我不知道是 C# 性能好还是 C++ 编写不好。
            【解决方案12】:

            当您认真对待性能时,您可能想要认真对待分析。

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

            【讨论】:

              【解决方案13】:

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

              【讨论】:

                【解决方案14】:

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

                通过使用指定 C.P.U 可用的最复杂的指令进行编译并使用算法知识告诉编译器在特定阶段使用 SIMD 指令,您可能(当然要付出足够的努力)超越这一点。

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

                编辑:

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

                【讨论】:

                  【解决方案15】:

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

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

                  【讨论】:

                    【解决方案16】:

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

                    【讨论】:

                    • 使用容器,但要明智地使用它们。不要将大件物品放入常用的容器中。
                    • 我看不出避免使用容器(我假设您的意思是例如 std::vector)对性能有很大帮助。尤其是考虑到 OP 所经历的性能差异。
                    • 根据我开发高性能科学计算的经验,从紧密循环中访问 std::vector 比使用原始数组要慢得多。它不是 std::vectors 错误,需要一些努力才能从容器中取出东西,可能是一些错误处理。看看OP的代码真的很有帮助。
                    • 我还应该注意,我猜测它主要基于数学,因为他在谈论位移优化
                    • 嗯,我相信你有理由得出这个结论 :) 此外,我不知道具体情况(如编译器、环境、容器/stl 的质量等)。不过,这确实让我有点吃惊,因为通过 [] 运算符的基于索引的访问对于 std::vector 和普通数组来说应该同样快,例如如果您查看 GCC 4.3.4 的源代码, op[] 很简单:reference operator[](size_type __n) { return *(this->_M_impl._M_start + __n);现在,at() 方法会进行边界检查,因此速度会慢一些 :)
                    【解决方案17】:

                    [笑话]第13行有错误[/笑话]

                    现在,说真的,没有源代码没有人可以回答这个问题。

                    但根据经验,C++ 比托管慢得多这一事实很可能表明内存管理和对象所有权问题的不同。

                    例如,如果您的算法在处理循环内进行任何动态内存分配,这将影响性能。如果您通过值传递重型结构,这将影响性能。如果你做不必要的对象副本,这将影响性能。异常滥用将导致性能下降。而且还在数。

                    我知道在参数名称后忘记“&”会导致数周的分析/调试:

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

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

                    【讨论】:

                      【解决方案18】:

                      C++ 不是必须使用类的语言。在我看来,在没有真正帮助的地方使用 OOP 方法是不合逻辑的。对于加密器/解密器,最好不要使用类;使用数组、指针,尽可能少地使用函数/类/文件。最好的加密系统由包含很少功能的单个文件组成。在您的函数运行良好后,您可以根据需要将其包装到类中。还要检查发布版本。速度差异很大

                      【讨论】:

                        【解决方案19】:

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

                        首先,顺便说一句。好的机器码:

                        • 使用寄存器比使用内存更频繁
                        • 很少分支(if/else、for 和 while)
                        • 比函数调用更频繁地使用内存
                        • 很少动态分配内存(从堆中)超过它已有的内存

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

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

                        【讨论】:

                          【解决方案20】:

                          这是我的 2 美分。

                          我用 C(和 C#)编写了 BlowFish 密码。 C# 与 C 几乎“相同”。

                          我是如何编译的(我现在不记得数字了,所以只记得比率):

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

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

                          我不是 100% 确定,但我的 C 版本编译为非常优化的汇编代码,汇编器输出看起来几乎与我找到的手写汇编器相同。

                          【讨论】: