83

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

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

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

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

托管还是非托管?

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

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

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

我看不出语言应该变慢的任何内在原因,而且我并不认为 C# 比 C++ 慢(从经验和缺乏好的解释来看)..

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

其他一些参考资料(其中谈到了一点,但没有解释为什么):

如果 C# 比 C++ 慢,为什么还要使用它?

4

12 回答 12

151

警告:您提出的问题非常复杂——可能比您意识到的要复杂得多。结果,这是一个长的答案。

从纯理论的角度来看,这可能有一个简单的答案: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++ 编译器的编译器(尽管有很多扩展),它产生的输出旨在在虚拟机上运行。

反之亦然:微软现在拥有 .NET Native,它将 C#(或 VB.NET)代码编译为本机可执行文件。这提供了通常更像 C++ 的性能,但保留了 C#/VB 的特性(例如,编译为本机代码的 C# 仍然支持反射)。如果您有性能密集型 C# 代码,这可能会有所帮助。

垃圾收集

从我所见,我会说垃圾收集是这三个因素中理解最差的。举一个明显的例子,这里的问题提到:“GC 也不会增加很多开销,除非您创建和销毁数千个对象 [...]”。实际上,如果您创建销毁数千个对象,垃圾收集的开销通常会相当低。.NET 使用了分代清道夫,它是多种复制收集器。垃圾收集器从指针/引用已知的“位置”(例如,寄存器和执行堆栈)开始工作可以访问。然后它“追逐”那些指向已在堆上分配的对象的指针。它检查这些对象是否有进一步的指针/引用,直到它跟随所有这些对象到达任何链的末端,并找到所有(至少可能)可访问的对象。在下一步中,它获取所有正在使用(或至少可能正在使用)的对象,并通过将所有对象复制到堆中管理的内存一端的一个连续块中来压缩堆。然后剩余的内存是空闲的(必须运行模终结器,但至少在编写良好的代码中,它们很少见,我暂时忽略它们)。

这意味着如果您创建和销毁大量对象,垃圾收集会增加很少的开销。垃圾回收周期所花费的时间几乎完全取决于已创建但未销毁的对象的数量。匆忙创建和销毁对象的主要后果只是 GC 必须更频繁地运行,但每个周期仍然会很快。如果您创建对象但不销毁它们,则 GC 将运行得更频繁,并且每个周期都会大大减慢,因为它会花费更多时间来追踪指向潜在活动对象的指针,并且会花费更多时间来复制仍在使用的对象。

为了解决这个问题,世代清除工作的假设是,已经“活着”了很长一段时间的物体可能会继续存活很长一段时间。基于此,它有一个系统,在该系统中,在一定数量的垃圾回收周期中幸存下来的对象将获得“永久”,并且垃圾回收器开始简单地假设它们仍在使用中,因此不是在每个周期都复制它们,而是简单地离开他们一个人。这是一个有效的假设,因为分代清理的开销通常比大多数其他形式的 GC 低得多。

“手动”内存管理通常同样难以理解。仅举一个例子,许多比较尝试假设所有手动内存管理也遵循一种特定模型(例如,最佳匹配分配)。这通常比许多人对垃圾收集的信念(例如,它通常使用引用计数完成的普遍假设)更接近现实(如果有的话)。

考虑到垃圾收集和手动内存管理的各种策略,很难在整体速度方面比较两者。尝试比较分配和/或释放内存(本身)的速度几乎可以保证产生的结果充其量是毫无意义的,最坏的情况是完全误导。

奖金主题:基准

由于相当多的博客、网站、杂志文章等声称在一个方向或另一个方向上提供“客观”证据,我也会在这个主题上投入两分钱。

这些基准中的大多数有点像青少年决定比赛他们的汽车,谁赢了就可以保留两辆车。但是,这些网站在一个关键方面有所不同:发布基准的人可以同时驾驶这两款车。出于某种奇怪的机会,他的车总是赢,而其他人都不得不接受“相信我,我真的开着你的车开得很快。”

编写一个糟糕的基准很容易,它产生的结果几乎没有任何意义。几乎任何人只要具备设计产生任何有意义的基准所需的技能,也有能力产生一个能够给出他决定想要的结果的基准。事实上,编写代码来产生特定结果可能比真正产生有意义结果的代码更容易。

正如我的朋友 James Kanze 所说,“永远不要相信不是你自己伪造的基准。”

结论

没有简单的答案。我有理由确定我可以掷硬币来选择获胜者,然后选择(比如说)1 到 20 之间的一个数字作为获胜的百分比,然后编写一些看起来像一个合理且公平的基准的代码,并且产生了已成定局的结论(至少在某些目标处理器上——不同的处理器可能会稍微改变百分比)。

正如其他人所指出的,对于大多数代码来说,速度几乎是无关紧要的。对此的推论(更经常被忽略)是,在速度确实很重要的小代码中,它通常很重要。至少根据我的经验,对于真正重要的代码,C++ 几乎总是赢家。肯定有一些有利于 C# 的因素,但实际上它们似乎被有利于 C++ 的因素所压倒。您当然可以找到可以表明您选择的结果的基准,但是当您编写真正的代码时,您几乎总是可以在 C++ 中使其比在 C# 中更快。写作可能(或可能不会)需要更多的技巧和/或努力,但这几乎总是可能的。

于 2011-03-16T20:50:45.770 回答
42

因为你并不总是需要使用(我用这个松散的)“最快”的语言?我开法拉利上班不是因为它更快...

于 2011-03-16T13:52:17.190 回答
23

大约 2005 年,来自本地/托管围栏两边的两位 MS 性能专家试图回答相同的问题。他们的方法和过程仍然令人着迷,并且结论今天仍然有效 - 我不知道有任何更好的尝试来给出明智的答案。他们指出,对绩效差异的潜在原因的讨论是假设性的,也是徒劳的,而真正的讨论必须有一些经验基础,以了解这种差异对现实世界的影响。

于是,老新陈雷蒙德里科马里亚尼为友谊赛制定了规则。选择中文/英文词典作为玩具应用程序上下文:简单到可以编码为业余爱好,但又足够复杂以演示非平凡的数据使用模式。规则一开始很简单——Raymond 编写了一个简单的 C++ 实现,Rico 将其逐行迁移到 C# ,没有任何复杂性,并且两个实现都运行了基准测试。随后,进行了多次优化迭代。

完整的细节在这里:1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14

这场泰坦对话极具教育意义,我衷心建议您深入了解——但如果您缺乏时间或耐心,杰夫·阿特伍德 (Jeff Atwood)将底线编成精美

在此处输入图像描述

最终,C++ 快了 2 倍——但最初,它慢了 13 倍。

正如 Rico总结的那样:

所以我为自己的惨败感到羞耻吗?几乎不。托管代码几乎不费吹灰之力就取得了非常好的结果。为了击败托管版本,Raymond 必须:

  • 写他自己的文件/io的东西

  • 编写自己的字符串类

  • 编写自己的分配器

  • 编写自己的国际地图

当然,他使用了可用的较低级别的库来执行此操作,但这仍然需要大量工作。你能把剩下的称为 STL 程序吗?我不这么认为。

这仍然是我的经验,11 年,谁知道以后有多少 C#/C++ 版本。

当然,这并非巧合,因为这两种语言惊人地实现了它们截然不同的设计目标。C# 希望在开发成本是主要考虑因素的情况下使用(仍然是大多数软件),而 C++ 则在您无需节省费用以从机器中榨取每一盎司性能的地方大放异彩:游戏、算法交易、数据-中心等

于 2016-05-08T18:37:04.460 回答
21

C++ 在性能上总是有优势。使用 C#,我无法处理内存,而且我有大量资源可供我完成工作。

您需要问自己更多的是哪个可以节省您的时间。机器现在非常强大,你的大部分代码都应该用一种可以让你在最短的时间内获得最大价值的语言来完成。

如果 C# 中有一个核心处理时间过长,那么您可以构建一个 C++ 并与 C# 互操作。

不要再考虑你的代码性能了。开始创造价值。

于 2011-03-16T13:52:48.290 回答
6

C#C++ 快。写得更快。对于执行时间,没有什么比分析器更好的了。

但是 C# 没有 C++ 可以轻松接口那么多的库。

而 C# 在很大程度上依赖于 windows...

于 2011-03-16T13:53:12.090 回答
4

顺便说一句,时间要求严格的应用程序不是用 C# 或 Java 编码的,主要是因为不确定何时执行垃圾收集。

在现代,应用程序或执行速度已不像以前那么重要。开发计划、正确性和稳健性是更高的优先事项。如果一个应用程序的高速版本有很多错误,崩溃很多或更糟,错过了进入市场或部署的机会,那么它就是不好的。

由于开发计划是优先事项,因此出现了加速开发的新语言。C# 就是其中之一。C# 还通过从 C++ 中删除导致常见问题的特性来帮助提高正确性和稳健性:一个例子是指针。

在大多数平台上,使用 C# 开发的应用程序和使用 C++ 开发的应用程序的执行速度差异可以忽略不计。这是因为执行瓶颈与语言无关,而通常取决于操作系统或 I/O。例如,如果 C++ 在 5 毫秒内执行一个函数,而 C# 使用 2 毫秒,并且等待数据需要 2 秒,那么在函数中花费的时间与等待数据的时间相比是微不足道的。

选择最适合开发人员、平台和项目的语言。朝着正确性、稳健性和部署的目标努力。应用程序的速度应该被视为一个错误:优先考虑它,与其他错误进行比较,并根据需要进行修复。

于 2011-03-16T19:05:36.873 回答
3

一种更好的看待它的方法比 C/C++ 慢,因为它是抽象的,而不是遵循棍子和泥浆范式。它被称为系统编程是有原因的,您针对谷物或裸机进行编程。这样做还可以提高您使用 C# 或 Java 等其他语言无法达到的速度。但是,唉,C 的根源都是以艰难的方式做事,所以你主要是要编写更多的代码并花更多的时间调试它。

C 也区分大小写,C++ 中的对象也遵循严格的规则集。例如,紫色冰淇淋蛋筒可能与蓝色冰淇淋蛋筒不同,尽管它们可能是蛋筒,但不一定属于蛋筒家族,如果您忘记定义您的蛋筒是什么蛋筒。因此,冰淇淋的特性可能是也可能不是克隆。现在是速度参数,C/C++ 使用堆栈和堆方法,这是裸机获得金属的地方。

使用 boost 库,您可以达到令人难以置信的速度,不幸的是大多数游戏工作室都坚持使用标准库。造成这种情况的另一个原因可能是因为用 C/C++ 编写的软件的文件大小往往很大,因为它是一个巨大的文件集合而不是单个文件。另请注意,所有操作系统都是用 C 编写的,所以通常我们为什么要问这个问题,什么可以更快?!

缓存也不比纯内存管理快,对不起,但这并没有散开。内存是物理的,缓存是软件为了提高性能而做的事情。人们还可以推断,如果没有物理内存缓存,根本就不存在。无论是自动还是手动,都必须在一定程度上管理内存这一事实并没有失效。

于 2017-02-28T08:22:26.287 回答
1

如果有更快的路线(C#),为什么要编写一个不需要太多 C++ 优化方式的小型应用程序?

于 2011-03-16T13:56:29.590 回答
1

除非您在特定系统上执行基准测试,否则实际上不可能获得问题的准确答案。但是,思考 C# 和 C++ 等编程语言之间的一些根本差异仍然很有趣。

汇编

执行 C# 代码需要执行 JIT 代码的附加步骤。关于将有利于 C++ 的性能。此外,JIT 编译器只能在 JIT 化的代码单元(例如方法)内优化生成的代码,而 C++ 编译器可以使用更积极的技术跨方法调用进行优化。

但是,JIT 编译器能够优化生成的机器代码以紧密匹配底层硬件,使其能够利用额外的硬件特性(如果存在)。据我所知,.NET JIT 编译器不会这样做,但可以想象它能够为 Atom 生成与 Pentium CPU 不同的代码。

内存访问

在许多情况下,垃圾收集架构可以创建比标准 C++ 代码更优化的内存访问模式。如果第一代使用的内存区域足够小,则可以留在 CPU 缓存内,从而提高性能。如果您创建和销毁大量小对象,则维护托管堆的开销可能小于 C++ 运行时所需的开销。同样,这高度依赖于应用程序。一项 Python 性能研究表明,由于更优化的内存访问模式,特定托管 Python 应用程序能够比编译版本更好地扩展。

于 2011-05-11T12:40:11.453 回答
0

不要让混淆!

  • 如果 C# 应用程序是在最好的情况下编写的,而 C++ 应用程序是在最好的情况下编写的,那么 C++ 会更快。
    很多原因是关于为什么 C++ 本质上比 C# 更快,例如 C# 在 Java 中使用类似于 JVM 的虚拟机。基本上高级语言的性能较低(如果在最佳情况下使用)。

  • 如果您是一位经验丰富的专业 C# 程序员,就像您是一位经验丰富的专业 C++ 程序员一样,那么使用 C# 开发应用程序比 C++ 更容易和快速。

这些情况之间的许多其他情况是可能的。例如,您可以编写 C# 应用程序和 C++ 应用程序,以使 C# 应用程序比 C++ 应用程序运行得更快。

在选择语言时,您应该注意项目的情况及其主题。对于一般商业项目,您应该使用 C#。对于需要高性能的项目,如视频转换器或图像处理项目,您应该选择 C++。

更新:

好的。让我们比较一下为什么 C++ 的大多数可能速度超过 C# 的一些实际原因。考虑一个编写好的 C# 应用程序和相同的 C++ 版本:

  • C# 使用 VM 作为执行应用程序的中间层。它有开销。
  • AFAIK CLR 无法优化目标机器中的所有 C# 代码。C++ 应用程序可以在目标机器上以最优化的方式编译。
  • 在 C# 中,最可能的运行时优化意味着最可能的快速 VM。无论如何,VM都有开销。
  • C# 是一种高级语言,因此它会为最终进程生成更多的程序代码行。(考虑汇编应用程序和 Ruby 之间的区别!C++ 和 C#/Java 等高级语言之间的情况相同)

如果您希望作为专家在实践中获得更多信息,请参阅此。它是关于 Java 的,但它也适用于 C#。

于 2016-11-06T18:18:28.287 回答
0

主要关注的不是速度,而是跨 Windows 版本和升级的稳定性。Win32 几乎不受 Windows 版本的影响,因此非常稳定。

当服务器退役和软件迁移时,使用 .Net 的任何事情都会发生很多焦虑,并且通常会对 .net 版本产生很多恐慌,但是 10 年前构建的 Win32 应用程序仍然像什么都没发生一样继续运行。

于 2017-06-17T03:45:37.663 回答
0

我已经专注于优化大约 15 年,并定期重写 C++ 代码,尽可能地大量使用编译器内在函数,因为 C++ 的性能通常远不及 CPU 的能力。通常需要考虑缓存性能。需要许多向量数学指令来替换标准 C++ 浮点代码。大量的 STL 代码被重新编写并且通常运行速度快很多倍。随着 CPU 接近其最佳性能,大量使用数据的数学和代码可以被重写并获得惊人的结果。

这在 C# 中是不可能的。比较它们的相对#real time# 性能确实是一个令人震惊的无知问题。C++ 中最快的一段代码将是每条汇编指令都针对手头的任务进行了优化,完全没有不必要的指令。在需要时使用每块内存,而不是复制 n 次,因为这是语言设计所需要的。每个所需的内存移动都与缓存协调工作。最终算法无法改进的地方,基于准确的实时要求,考虑到准确性和功能性。

然后,您将接近最佳解决方案。

将 C# 与这种理想情况进行比较是令人震惊的。C# 无法竞争。事实上,我目前正在重新编写一大堆 C# 代码(当我说重新编写时,我的意思是完全删除和替换它),因为它甚至不在同一个城市,更不用说实时处理繁重的球场了表现。

所以,请不要再自欺欺人了。C# 很慢。死慢。所有软件都在变慢,而 C# 正在使这种速度下降变得更糟。所有软件都使用汇编程序中的 fetch 执行周期运行(你知道 - 在 CPU 上)。你使用了 10 倍的指令;它会慢10倍。你削弱了缓存;它会变得更慢。您将垃圾收集添加到实时软件中,然后您经常被愚弄认为代码运行“正常”,只是偶尔会出现代码“有一段时间有点慢”的那一刻。

尝试将垃圾收集系统添加到每个循环都很重要的代码中。我想知道股票市场交易软件是否有垃圾收集(你知道——在耗资 3 亿美元的新海底电缆上运行的系统上?)。我们可以每 2 秒腾出 300 毫秒吗?航天飞机上的飞行控制软件怎么样——GC在那里好吗?高性能车辆中的发动机管理软件怎么样?(一个赛季的胜利可以价值数百万)。

实时垃圾回收是彻底的失败。

所以不,强调,C++ 要快得多。C# 是一个倒退。

于 2017-09-05T08:45:10.147 回答