c# - C# 真的比说 C++ 慢吗?

标签 c# c++ performance

我一直想知道这个问题有一段时间了。

当然,C# 中有些东西没有针对速度进行优化,因此使用这些对象或语言调整(如 LinQ)可能会导致代码变慢。

但是,如果您不使用任何这些调整,而只需比较 C# 和 C++ 中的相同代码段(很容易将一个转换为另一个)。真的会慢很多吗?

我看到一些比较表明 C# 在某些情况下可能更快,因为理论上 JIT 编译器应该实时优化代码并获得更好的结果:

Managed Or Unmanaged?

我们应该记住,JIT 编译器实时编译代码,但这是 1 次开销,相同的代码(一旦到达并编译)不需要在运行时再次编译。

GC 也不会增加很多开销,除非您创建和销毁数以千计的对象(例如使用 String 而不是 StringBuilder)。在 C++ 中这样做也会很昂贵。

我想提出的另一点是 .Net 中引入的 DLL 之间更好的通信。 .Net 平台的通信比基于托管 COM 的 DLL 好得多。

我没有看到为什么语言应该变慢的任何内在原因,而且我真的不认为 C# 比 C++ 慢(从经验和缺乏很好的解释来看)。

那么,用 C# 编写的一段相同代码会比用 C++ 编写的相同代码慢吗?
如果是这样,那么为什么?

其他一些引用资料(稍微谈了一下,但没有解释为什么):

Why would you want to use C# if its slower than C++?

最佳答案

警告:您提出的问题确实非常复杂——可能比您意识到的要复杂得多。结果,这是一个很长的答案。
从纯理论的角度来看,可能有一个简单的答案:C#(可能)没有任何东西真正阻止它像 C++ 一样快。然而,尽管有这个理论,但在某些情况下,它在某些事情上较慢是有一些实际原因的。
我将考虑三个基本差异领域:语言特性、虚拟机执行和垃圾收集。后两者经常一起去,但可以独立,所以我将它们分开看。
语言特点
C++ 非常重视模板和模板系统中的特性,这些特性主要是为了允许在编译时尽可能多地完成,因此从程序的角度来看,它们是“静态的”。模板元编程允许在编译时执行完全任意的计算(即模板系统是图灵完备的)。因此,基本上任何不依赖于用户输入的东西都可以在编译时计算,所以在运行时它只是一个常量。但是,对此的输入可以包括类型信息之类的内容,因此您在 C# 中在运行时通过反射所做的大量工作通常是在编译时通过 C++ 中的模板元编程完成的。不过,在运行速度和多功能性之间肯定存在权衡——模板可以做什么,它们静态地做,但它们根本不能做反射可以做的一切。
语言特征的差异意味着几乎任何仅通过将某些 C# 音译为 C++(反之亦然)来比较这两种语言的尝试都可能产生介于无意义和误导之间的结果(对于大多数其他语言对也是如此)同样)。一个简单的事实是,对于任何超过几行代码的东西,几乎没有人可能以相同的方式(或接近相同的方式)使用这些语言,这样的比较会告诉您有关这些语言如何使用的任何信息在现实生活中工作。
虚拟机
就像几乎所有合理的现代 VM 一样,Microsoft 的 .NET 可以并且将会进行 JIT(又名“动态”)编译。但这代表了许多权衡。
首先,优化代码(与大多数其他优化问题一样)在很大程度上是一个 NP 完全问题。对于真正琐碎/玩具程序以外的任何东西,您几乎可以保证您不会真正“优化”结果(即,您不会找到真正的最佳值)——优化器只会让代码比它更好以前是。然而,相当多的众所周知的优化需要大量的时间(通常还有内存)来执行。对于 JIT 编译器,用户在编译器运行时等待。大多数更昂贵的优化技术被排除在外。静态编译有两个优点:首先,如果它很慢(例如,构建一个大型系统),它通常在服务器上执行,没有人花时间等待它。其次,一个可执行文件可以生成一次,并被很多人多次使用。第一个最小化优化成本;第二个在执行大量执行时摊销小得多的成本。
正如原始问题(以及许多其他网站)中提到的,JIT 编译确实有可能对目标环境有更大的了解,这应该(至少在理论上)抵消了这一优势。毫无疑问,这个因素至少可以部分抵消静态编译的缺点。对于一些相当特定类型的代码和目标环境,它甚至可以超过静态编译的优势,有时甚至相当显着。然而,至少在我的测试和经验中,这是相当不寻常的。与目标相关的优化似乎要么产生相当小的差异,要么只能(无论如何都是自动地)应用于相当特定类型的问题。很明显,如果您在现代机器上运行相对较旧的程序,就会发生这种情况。用 C++ 编写的旧程序可能会被编译为 32 位代码,并且即使在现代 64 位处理器上也会继续使用 32 位代码。用 C# 编写的程序将被编译为字节码,然后 VM 将编译为 64 位机器码。如果这个程序从作为 64 位代码运行中获得了实质性的好处,那可能会带来很大的优势。在 64 位处理器相当新的短时间内,这种情况发生了很多。不过,最近可能受益于 64 位处理器的代码通常可以静态编译为 64 位代码。
使用 VM 还可以提高缓存使用率。 VM 的指令通常比本地机器指令更紧凑。它们中的更多可以放入给定数量的缓存内存中,因此您更有可能在需要时将任何给定代码放入缓存中。这有助于保持 VM 代码的解释执行比大多数人最初预期的更具竞争力(在速度方面)——您可以在一次缓存未命中所花费的时间内在现代 CPU 上执行大量指令。
还值得一提的是,这个因素在两者之间并不一定不同。没有什么可以阻止(例如)C++ 编译器生成旨在在虚拟机(有或没有 JIT)上运行的输出。事实上,微软的 C++/CLI 几乎就是这样——一个(几乎)符合 C++ 编译器(尽管有很多扩展),它产生旨在在虚拟机上运行的输出。
反之亦然:Microsoft 现在拥有 .NET Native,它将 C#(或 VB.NET)代码编译为本地可执行文件。这提供了通常更像 C++ 的性能,但保留了 C#/VB 的特性(例如,编译为 native 代码的 C# 仍然支持反射)。如果您有性能密集型 C# 代码,这可能会有所帮助。
垃圾收集
从我所见,我想说垃圾收集是这三个因素中最难理解的。举一个明显的例子,这里的问题提到:“GC 也不会增加很多开销,除非您创建和销毁数千个对象 [...]”。实际上,如果您创建和销毁数以千计的对象,垃圾收集的开销通常会相当低。 .NET 使用分代清道夫,它是多种复制收集器。垃圾收集器从已知可访问指针/引用的“位置”(例如,寄存器和执行堆栈)开始工作。然后它“追逐”那些指向已在堆上分配的对象的指针。它检查这些对象是否有进一步的指针/引用,直到它跟随所有对象到达任何链的末端,并找到所有(至少可能)可访问的对象。在下一步中,它获取所有正在使用(或至少可能正在使用)的对象,并通过将所有对象复制到堆中管理的内存一端的连续块中来压缩堆。然后剩余的内存是空闲的(必须运行模终结器,但至少在编写良好的代码中,它们很少见,我暂时将忽略它们)。
这意味着如果您创建和销毁大量对象,垃圾收集会增加很少的开销。垃圾回收周期所用的时间几乎完全取决于已创建但未销毁的对象数量。匆忙创建和销毁对象的主要后果很简单,GC 必须更频繁地运行,但每个循环仍然会很快。如果您创建对象而不销毁它们,GC 将更频繁地运行,并且每个循环都会显着变慢,因为它花费更多时间追逐指向潜在事件对象的指针,并且花费更多时间复制仍在使用的对象。
为了解决这个问题,分代清理工作的前提是假设已经“存活”了一段时间的对象可能会继续存活一段时间。基于此,它有一个系统,其中在一定数量的垃圾收集周期中幸存下来的对象被“终身使用”,垃圾收集器开始简单地假设它们仍在使用中,因此它不是在每个周期都复制它们,而是简单地离开他们一个人。这是一个足够有效的假设,以至于分代清理通常比大多数其他形式的 GC 具有低得多的开销。
“手动”内存管理通常也同样鲜为人知。仅举一个例子,许多比较尝试假设所有手动内存管理也遵循一个特定模型(例如,最佳分配)。这通常比许多人对垃圾收集的信念(例如,通常使用引用计数完成的普遍假设)更接近现实(如果有的话)。
鉴于垃圾收集和手动内存管理的策略多种多样,很难在整体速度方面比较两者。尝试比较分配和/或释放内存(单独)的速度几乎可以保证产生充其量毫无意义的结果,最坏的情况是完全误导。
额外主题:基准
由于相当多的博客、网站、杂志文章等声称提供了一个或另一个方向的“客观”证据,因此我也将在该主题上投入我的两分钱。
大多数这些基准测试有点像青少年决定参加他们的汽车比赛,无论谁获胜,都可以保留两辆汽车。不过,这些网站在一个关键方面有所不同:发布基准的人可以同时驾驶这两辆车。一个奇怪的机会,他的车总是赢,其他人都不得不满足于“相信我,我真的把你的车开得尽可能快。”
编写一个糟糕的基准测试很容易产生几乎没有任何意义的结果。几乎任何拥有设计基准所必需的技能的人几乎都可以产生任何有意义的东西,也有能力产生一个能够给出他决定他想要的结果的基准。事实上,编写产生特定结果的代码可能比编写真正产生有意义结果的代码更容易。
正如我的 friend James Kanze 所说,“永远不要相信一个你没有伪造自己的基准。”
结论
没有简单的答案。我有理由确定我可以抛硬币来选择获胜者,然后在(比如)1 到 20 之间选择一个数字作为获胜的百分比,并编写一些看起来像合理和公平基准的代码,并且产生了既定的结论(至少在某些目标处理器上——不同的处理器可能会稍微改变百分比)。
正如其他人指出的那样,对于大多数代码,速度几乎无关紧要。其推论(更常被忽略)是,在速度很重要的小代码中,它通常很重要。至少根据我的经验,对于真正重要的代码,C++ 几乎总是赢家。肯定有利于 C# 的因素,但在实践中,它们似乎被有利于 C++ 的因素所抵消。您当然可以找到指示您选择结果的基准,但是当您编写真正的代码时,您几乎总是可以在 C++ 中使其比在 C# 中更快。写作可能(也可能不会)需要更多的技巧和/或努力,但实际上总是可能的。

关于c# - C# 真的比说 C++ 慢吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5326269/

相关文章:

c++ - 如何将屏幕外渲染到 Linux 上的图像?

java - ArrayListMultimap 和 ArrayList 哪个性能好?

c# - 何时使用 "^"运算符

c# - 如果在数据库中找不到记录,则返回 (RecordNotFound) 异常或 null?

c# - 过滤字符串并从中创建新的

c++ - 将函数作为模板参数传递

c++ - SPOJ :ENIGMATH - PLAY WITH MATH

c# - 使用 sp_executesql 运行时相同的 SQL 查询很快,如果在查询分析器中作为动态查询执行则非常慢,为什么?

python - NumPy 数学函数比 Python 更快吗?

c# - 无法创建 C# COM DLL。是代码吗?项目属性?安装程序属性? 32-64 位?什么?