C++比C#快多少?

315

还是说现在情况反过来了?

据我所知,有些领域 C# 比 C++ 更快,但我从未有勇气自己测试。

希望您能详细解释这些差异或指导我获取相关信息。


9
为了防止发布更多随意的基准测试结果,进行了保护。如果你认为你有说服力,你需要10个声望来证明。 - Robert Harvey
1
这几乎是一个无关紧要的问题,因为我们生活在一个可以将IL转换为CPP并从那里进行优化的时代:https://docs.unity3d.com/Manual/IL2CPP.html - pixelpax
检查数组越界访问的语言永远不会比没有这种检查的语言表现更好。 - Seva Alekseyev
@SevaAlekseyev 这不是语言造成的,而是编译器造成的。除了明显的原因之外,C++之所以如此快,其中一个原因在于C++编译器已经存在了过去35年(如果不是更长时间)。没有什么能阻止C#编译器随着时间的推移变得更好。对于您提到的情况,请阅读这篇文章https://dev59.com/5WQn5IYBdhLWcg3wmIAs。 - Trap
29个回答

415

像C#或Java这样的基于字节码的语言,如果有JIT编译器,理论上它们可以和C++一样快。但是长期以来,C++代码在许多情况下仍然明显更快,这主要是因为更高级别的JIT优化实现比较困难,而且真正酷炫的优化刚刚才开始问世。

因此,在许多情况下,C++确实更快。但这只是答案的一部分。C++代码实际上更快的情况是高度优化的程序,由专业程序员彻底地优化了代码。这不仅非常耗时(因此昂贵),而且通常会由于过度优化而导致错误。

另一方面,解释型语言中的代码在运行时的后续版本(.NET CLR或Java VM)中会变得更快,而不需要任何操作。 JIT编译器可以进行许多有用的优化,而在具有指针的语言中这些优化是不可能的。此外,一些人认为垃圾回收应该通常与手动内存管理一样快或更快,并且在许多情况下确实如此。您通常可以在C ++或C中实现并实现所有这些,但这将更加复杂且容易出错。

正如Donald Knuth所说,“过早的优化是万恶之源”。如果您确实确定您的应用程序将主要由非常性能关键的算术组成,并且它将成为瓶颈,而且它肯定会比C++快,那么您可以选择C ++。否则,请首先使用最适合您的语言正确地实现您的应用程序,然后在运行速度过慢时找出性能瓶颈,然后考虑如何优化代码。在最坏的情况下,您可能需要通过外部函数接口调用C代码,以便仍然可以使用低级别语言编写关键部分。

请记住,优化正确的程序相对容易,但纠正优化过的程序要困难得多。

给出实际速度优势的百分比是不可能的,这在很大程度上取决于您的代码。在许多情况下,编程语言的实现甚至都不是瓶颈。对于在http://benchmarksgame.alioth.debian.org/进行的基准测试要持高度怀疑态度,因为这些测试主要测试算术代码,这与您的代码大概率完全不同。


112
解释:当解释型语言的版本更新时,其中的代码会变得更快。同样地,当编译器版本更新时,通过该编译器编译的代码也会变得更快。翻译:解释型语言中的代码在后续版本的运行时中会变得更快,同样地,由更好版本的编译器编译的代码也会变得更快。 - Martin York
55
实际上至少有一个原因:JIT 必须快速,不能花费时间在 C++ 编译器可用的各种高级优化上。 - Nemanja Trifunovic
72
“但过度优化也常常导致错误。” [急需引用]. 我在一个国家实验室工作,我们对代码进行了极度优化。这通常并不会导致出现错误的代码。 - Todd Gamblin
42
优化一个正确的程序相对较容易,但是纠正一个经过优化的程序则更加困难。 - gradbot
22
英文内容翻译:Inge: 不确定你是否理解得正确。是的,C# 是用另一种语言实现的,但 JIT 编译器正在生成机器码,因此它不是一种解释性语言。因此,它不受其 C++ 实现的限制。我不太确定为什么您认为将某些管理者添加到某物中本质上会使其更快。 - Martin Probst
显示剩余23条评论

142

我先要反对此问题被接受的(且得到大量赞同的)答案,声明:

JIT编译代码比经过适当优化的C++(或其他没有运行时开销的语言)程序运行得慢确实有很多原因,包括:

  • 在运行时用于JIT编译代码的计算周期定义上不可用于程序执行。

  • JIT编译器中的任何热点都会与CPU中指令和数据高速缓存竞争。 我们知道高速缓存是性能的主导因素,并且像C++这样的本地语言没有这种类型的竞争,这是通过设计实现的。

  • 运行时优化器的时间预算必然比编译时优化器的时间预算更加受限制(正如另一位评论者所指出的)

底线:最终,你几乎肯定可以使用C++创建比C#更快的实现

现在,话虽如此,有多快真的无法量化,因为有太多变量:任务、问题领域、硬件、实现质量以及许多其他因素。 您必须在您的情况下运行测试以确定性能差异,然后决定是否值得额外的努力和复杂性。

这是一个非常长且复杂的主题,但我认为值得提及的是,C#的运行时优化器非常出色,并且能够执行某些动态优化,而这些优化对于使用编译时(静态)优化器的C++来说根本不可用。 即使如此,优势仍然通常深深地掌握在本机应用程序的手中,但动态优化器是上述“几乎”限定语的原因。

--

关于相对性能,我也对其他答案中看到的数字和讨论感到不安,因此我想发表一下意见,并同时支持我上面所说的话。

这些基准测试的一个很大的问题是,您不能编写C++代码,就好像您正在编写C#代码并期望获得代表性的结果(例如,在C++中执行数千个内存分配将为您带来可怕的数字)。

相反,我编写了稍微更符合惯例的C++代码,并与@Wiory提供的C#代码进行了比较。 我对C++代码进行了两个重要的更改:

  1. 使用了vector :: reserve()

  2. 将2D数组展平为1D,以实现更好的缓存局部性(连续块)

C#(.NET 4.6.1)

private static void TestArray()
{
    const int rows = 5000;
    const int columns = 9000;
    DateTime t1 = System.DateTime.Now;
    double[][] arr = new double[rows][];
    for (int i = 0; i < rows; i++)
        arr[i] = new double[columns];
    DateTime t2 = System.DateTime.Now;

    Console.WriteLine(t2 - t1);

    t1 = System.DateTime.Now;
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < columns; j++)
            arr[i][j] = i;
    t2 = System.DateTime.Now;

    Console.WriteLine(t2 - t1);
}

运行时间(发布版):初始化:124毫秒,填充:165毫秒

C++14(Clang v3.8 / C2)

#include <iostream>
#include <vector>

auto TestSuite::ColMajorArray()
{
    constexpr size_t ROWS = 5000;
    constexpr size_t COLS = 9000;

    auto initStart = std::chrono::steady_clock::now();

    auto arr = std::vector<double>();
    arr.reserve(ROWS * COLS);

    auto initFinish = std::chrono::steady_clock::now();
    auto initTime = std::chrono::duration_cast<std::chrono::microseconds>(initFinish - initStart);

    auto fillStart = std::chrono::steady_clock::now();

    for(auto i = 0, r = 0; r < ROWS; ++r)
    {
        for (auto c = 0; c < COLS; ++c)
        {
            arr[i++] = static_cast<double>(r * c);
        }
    }

    auto fillFinish = std::chrono::steady_clock::now();
    auto fillTime = std::chrono::duration_cast<std::chrono::milliseconds>(fillFinish - fillStart);

    return std::make_pair(initTime, fillTime);
}

运行时间(发布版):初始化时间:398微秒(是的,那是微秒),填充时间:152毫秒

总运行时间:C#:289毫秒,C ++:152毫秒(大约快了90%)

观察结果:

  • 将C#实现更改为相同的一维数组实现产生了初始化时间:40毫秒,填充时间:171毫秒,总时间:211毫秒(C++仍然比C#快近40%)。

  • 在C ++中编写“快速”代码比在任何一种语言中编写“常规”代码要困难得多。

  • 在C ++中很容易出现性能不佳的情况;我们在未保留向量的性能方面看到了这一点。还有许多类似的陷阱。

  • 考虑到运行时所发生的所有事情,C#的性能相当惊人。而且这种性能相对容易获取。

  • 更多关于C++和C#性能比较的轶闻数据:https://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=gpp&lang2=csharpcore

总的来说,C++赋予你更多控制性能的权利。您想使用指针?引用?堆栈内存?堆内存?使用动态多态性还是消除虚拟表的运行时开销,通过静态多态性(通过模板/ CRTP)?在C++中,您必须......嗯,有机会自己做出所有这些选择(以及更多选择),理想情况下,使您的解决方案最好地解决所涉及的问题。

请问您是否真正需要或想要这种控制权,因为即使对于上面的简单示例,您也可以看到虽然性能有了显着提高,但访问所需投资更深入。


18
@Quonux 感谢您的评论。当然,这不是一个“真正的程序”。基准测试的目的是重构在本页面其他地方提供的C#基准测试,以证明JIT编译的代码比本地代码更快 - 实际上并非如此,并且该基准测试可能会误导新手。 - U007D
10
@Quonux,你为什么要写成那样?就是像你这样的人让我不喜欢 Stack Overflow。 - Markus Knappen Johansson
5
@MarkusKnappenJohansson,我今天过得不太好;)我也只是个普通人,取消了我的踩,但我的观点仍然适用。哦,请不要因为有一些“愚蠢”的人而不喜欢SO :)。祝你好运。 - Quonux
14
完全误导性的基准测试。在 C++ 版本中,你只是保留了一部分内存(然后惊叹于这个操作只需微秒级别的执行时间)。在 C# 版本中,你创建了 5000 个数组(在内存中实例化对象)。C++ 比 C# 快...但差距远不到40%...现在更接近于<10%。你的示例说明程序员应该坚持使用他们擅长的语言(而从你的个人资料中可以看出,你是一位职业 C++ 程序员)。在 C# 中,你可以使用 2D 数组 int[,]...接下来是一个例子。 - nikib3ro
4
据我所知,你的C++示例代码只是提前分配内存。适当的C#实现应该简单地写作“List<double> arrs = new List<double>(ROWS*COLS)”,它会为以一维格式索引二维数组所需的内存进行分配(例如你在C++中所做的)。没有必要分配一个二维数组并手动将其展平 - 在预测试中大量迭代是性能不佳的原因。我想C#中的开销仍然会更多,但不会很多。 - Jax
显示剩余12条评论

98

它快了五个橙。或者说:没有(正确的)笼统答案。C++是一种静态编译语言(但是,也有基于分析数据的优化),而C#则通过JIT编译器运行。存在如此多的差异,以至于像“更快多少”的问题无法回答,甚至无法给出数量级。


223
你有支持你那离谱的五个橙子说法的任何证据吗?我的实验都显示最多只有两个橙子,但在进行模板元编程时可以提升三个芒果。 - Alex
2
同意,根据我的计算,我也得到了两个橙子,但我没有看到任何芒果。 - Sushant

67

根据我的经验(我曾经与这两种语言密切合作过),相对于C++,C# 的主要问题是内存消耗比较高,我没有找到一种好的方式来控制它。正是由于内存消耗会最终导致 .NET 软件变慢。

另一个因素是JIT编译器不能花太多时间进行高级优化,因为它在运行时运行,如果它需要太多时间,最终用户将会注意到。而另一方面,C++编译器有足够的时间在编译时进行优化。在我看来,这个因素比内存消耗少得多。


6
在工作中的一个项目中,我们需要挖掘大量的数据,包括同时在内存中保存多个G的数据并对其执行昂贵的计算--这需要精确控制所有分配,C++几乎是唯一的选择。对于C++点赞。另一方面,那只是一个项目,我们大部分时间都在编写与慢模拟器交互的系统,并且调试可能会非常困难,因此我希望我们可以为所有其他事情使用一个优化程序员时间的语言。 - Bogatyr
7
我知道“Dispose模式”,但它对托管内存没有帮助。 - Nemanja Trifunovic
10
仅执行Dispose方法可以确保其被调用。但是,处理不会释放垃圾回收的内存。Dispose方法仅旨在清理非托管资源(如文件句柄),与内存管理无关。 - doug65536
6
即使增加RAM总是可行的(事实并非如此 - 想象微软给每个MS Office用户都添加RAM),这也解决不了问题。内存是分层的,C#程序会比C++程序有更多的缓存未命中。 - Nemanja Trifunovic
2
谢谢你,提供答案的人,而不仅是说“噢,这取决于……啥的”。 - sebjwallace
显示剩余5条评论

38

在特定的场景中,C++仍然占据优势(而且未来几年内将继续如此),这种情况发生在多态决策可以在编译时预先确定的情况下。

通常,封装和延迟决策是一件好事,因为它使得代码更具动态性,更易于适应不断变化的需求,并更容易用作框架。这就是为什么C#中的面向对象编程非常高效,并可归纳为“泛化”一词的原因。不幸的是,这种特定类型的泛化会带来运行时成本。

通常,这种成本不重要,但有些应用程序中虚方法调用和对象创建的开销可能会有所差异(特别是因为虚方法阻止其他优化,例如方法调用内联)。这就是C++具有巨大优势的地方,因为您可以使用模板来实现一种不同类型的泛化,这种泛化对运行时没有影响,但不一定比面向对象编程少多少多态性。实际上,构成面向对象编程的所有机制都可以仅使用模板技术和编译时解析进行建模。

在这种情况下(尽管他们通常局限于特殊问题领域),C++胜过C#和类似的语言。


6
实际上,Java虚拟机(可能包括.NET)会尽力避免动态分派。基本上,如果有办法避免多态性,你可以相当确定你的虚拟机会这样做。 - Martin Probst
9
@crtracy:你正在进行赌注,但没有使用高性能计算应用程序。考虑到天气预报、生物信息学和数字模拟等领域,C++在这些领域的性能优势不会缩小,因为没有其他代码可以在相同抽象级别下实现可比较的性能。 - Konrad Rudolph
3
顺便提一下,为了避免误解,我承认 C# 元编程是更强大的,因为你可以在运行时使用它。但是我对你所说的“数量级更快”的说法持有异议,这完全是错误的。 - Konrad Rudolph
5
你的说法有些混淆。你具体声称“在元编程的上下文中,C#比C++快了数个数量级”,而不是“使用预编译代码比解释代码快数个数量级”。顺便说一下,你声称运行时代码生成比编译时代码生成“更加通用”也是明显错误的——它们都有优缺点。编译时代码生成使用类型系统提供静态类型安全性,而运行时代码生成无法做到这一点(它可以提供强类型安全性,但不是静态类型安全性)。 - Konrad Rudolph
6
我认为你忽略了这个答案的核心要点。当然,你可以通过打破封装并降至低级结构来解决此问题 - 你可以在(大多数)任何语言中编写汇编代码。使C++(几乎)独特和非常适合高性能编程的是,你可以构建高级抽象,而这些抽象不需要任何运行时成本。因此,在C++中,你不需要编写类似于汇编代码才能获得最佳性能:一个良好编写的sort(arr, generic_comparer)将与C++中手写循环一样有效。但这在C#中永远不可能实现。 - Konrad Rudolph
显示剩余24条评论

21

C++(或C语言)可以对数据结构进行精细的控制。如果你想要位运算,这是可行的。而大型管理的Java或.NET应用程序(如OWB,Visual Studio 2005)使用Java/.NET库的内部数据结构时会带来很多负担。我曾经见过OWB设计会话使用超过400 MB的RAM,而用于立方体或ETL设计的BIDS也可能会达到几百MB。

在可预测的工作负载下(例如大多数重复处理过程的基准测试),即时编译器可以为您生成优化得足够好的代码,以至于没有实际的差异。

在我的看法中,在大型应用程序中,区别不是即时编译器,而是代码本身使用的数据结构。当应用程序需要较多的内存时,CPU缓存的利用率就会变低,导致缓存未命中。现代CPU上的缓存未命中非常昂贵。C或C++的优势在于可以优化数据结构的使用方式,以使其与CPU缓存协同工作。


曾在Cadence Design Systems工作,时间为2010年至2013年。负责维护一个大型代码库,该代码库可以追溯到1989年左右,是一个Linux桌面应用程序。花费数月时间调查和修复与缓存和页面默认值相关的问题,取得了巨大的收益。C++提供的精细控制是实现这一目标的工具。同时发现我们在迁移到x64系统时出现了2倍的减速,后来解决了同样类型的问题:CPU缓存中指针和注册表的使用。C++确实是无与伦比的,也许RUST现在也能够做到同样的事情。 - rsacchettini

19

对于图形方面,标准的C# Graphics类比通过C/C++访问的GDI要慢得多。 我知道这与语言本身无关,更多的是与整个.NET平台有关,但Graphics是作为GDI替代品提供给开发人员的,其性能太差了,我甚至不敢用它进行图形处理。

我们有一个简单的基准测试来查看图形库的速度,那就是在窗口中绘制随机线条。 C++/GDI仍然可以轻松绘制10000条线,而C#/Graphics在实时绘制1000条线时就会遇到困难。


5
我对你的回答很感兴趣。您是否使用不安全的代码和LockBits,自己绘制随机线条进行了相同基准测试? 那将是一个很有意思的研究方向。 - Pedery
2
@Pedery 没有,我只是以最基本的方式使用 GDI 和 .NET.Graphics。你所说的“自己画随机线条”是什么意思? - QBziZ
1
那么,您或许应该考虑测试一下,以获得更真实的指标来了解 C# 的速度有多快。这里有一个很好的技术概述:http://www.bobpowell.net/lockingbits.htm - Pedery
6
这不是我们想要做的事情,自己在帧缓冲区中放置单独的像素。如果必须自己实现所有内容,那么有API/平台可供编码的意义何在? 对我来说,这不成立。我们在GDI中绘制线条时从未需要在帧缓冲区中放置单独的像素,我们也不打算在.NET中这样做。 在我看来,我们使用了一个现实的度量标准,而.NET变得很慢。 - QBziZ
1
我对blob detection的了解只是一点点,但仅仅给出一个时间并不能证明什么。你写过C++版本的吗?JavaScript版本的呢?你有没有将它们与C#版本进行比较? 此外,我认为blob detection并没有使用很多图形原语。如果我错了,请纠正我,但我猜测它是在像素上执行操作的统计算法。 - QBziZ
显示剩余4条评论

14
垃圾回收是Java#不能用于实时系统的主要原因。
  • GC会在什么时候发生?
  • 需要多长时间?
这是不确定的。

5
我并不是Java的忠实粉丝,但这并不意味着Java不能使用实时友好的垃圾回收机制。 - Zan Lynx
5
如果你感兴趣,可以找到很多实时垃圾回收(GC)的实现。(GC是一个“充满”研究论文的领域。) - Arafangion
11
这个论点毫无意义,Windows(和Linux)不是实时操作系统。你的C++代码也可能随时被换出18毫秒的时间片。 - H H
2
@HenkHolterman 是的,但你可以用汇编语言编写一个引导程序,将其与内核引导程序绑定到应用程序中,并直接针对硬件执行C++应用程序(在RT中)。这在C#中是做不到的,我看到的所有努力都只是模仿预编译的C#汇编,并使用大量的C代码,这使得使用C#毫无意义。读到这些都有点好笑,因为没有.NET框架,C#真的是毫无用处的。 - zackery.fix
@zackery.fix 哈哈,不是这样的。我并不是说C#会是我在只有32 kiB内存的嵌入式系统中的首选语言,但这只是因为优化C#或.NET在这种环境下的任何部分都没有什么意义。但如果你至少有半兆字节的内存,那就没问题了。而且我以前写过一个用C#编写的操作系统,我可以告诉你它很棒。当然,没有.NET框架。有趣的是,你说C#只能“模拟”预编译的汇编语言,而在C++中,你可以使用汇编语言来实现这一点。所以你能否用C++编写操作系统呢? :) - Luaan
显示剩余6条评论

13

C/C++在处理大型数组或对任何大小的数组进行重复循环/迭代的程序中表现更加优异。这就是为什么C/C++中的图形通常更快的原因,因为几乎所有的图形操作都基于繁重的数组操作。由于.NET中的安全检查,数组索引操作非常慢,特别是多维数组(是的,矩形形式的C#数组比锯齿形状的C#数组更慢)。

如果直接使用指针并避免使用Boost、std::vector和其他高级容器,尽可能使用老式的数组,那么C/C++的优势最为明显,每个小函数都应该使用inline。是的,相对于Java或C#,你需要更多的代码行来完成同样的事情,因为你要避免使用高级容器。如果你需要一个动态大小的数组,你只需要记住将new T[]与相应的delete[]语句配对(或使用std::unique_ptr)-额外速度的代价是必须更加谨慎地编写代码。但作为交换,你可以摆脱托管内存/垃圾回收的开销,在Java和.NET中,这些开销可能占执行时间的20%或更多,以及那些大规模托管内存数组索引的成本。在某些特定情况下,C++应用程序也可以从一些巧妙的编译器开关中受益。

我是C、C++、Java和C#的专业程序员,最近有一个罕见的机会在这三种语言中实现完全相同的算法程序。该程序有很多数学和多维数组操作。我在这3种语言中进行了大量优化。结果与我通常在不太严格的比较中看到的结果相似:Java比C#快约1.3倍(大多数JVM都比CLR更优化),而C++原始指针版本比C#快约2.1倍。请注意,C#程序仅使用了安全代码-我的意见是,在使用unsafe关键字之前,最好将其编码为C++。

如果有人认为我对C#有偏见,那么我会说C#可能是我最喜欢的语言。它是我迄今为止遇到的最逻辑、直观和快速的开发语言。我所有的原型都是用C#制作的。C#语言在许多小而微妙的方面优于Java(是的,我知道微软有机会通过晚入局并抄袭Java来修复Java的许多缺点)。向Java的Calendar类干杯?如果微软真正努力优化CLR和.NET JITter,C#可能会严重占领市场。我真的很惊讶他们还没有这样做-在C#语言中做了很多正确的事情,为什么不跟进强大的编译器优化呢?也许我们都应该请求一下。


3
你只需记得将你的“new T[]”与相应的“delete[]”配对,不需要写解释。— 不对,你不需要这样做。有“std::unique_ptr”可以为你完成这个工作。 - emlai
假设您在图形方面编写了一些内容,为什么要在C#中编写安全代码?您是否考虑过使用不安全的代码并进行比较? - Peter

11

我们不得不确定C#在性能上是否与C++相当,我为此编写了一些测试程序(使用Visual Studio 2005编写两种语言)。结果表明,在没有垃圾回收并且仅考虑语言(而非框架)的情况下,C#与C++基本具有相同的性能。在内存分配方面,C#比C++更快,并且在数据大小超出缓存行边界时,C#具有轻微的确定性优势。但是,所有这些最终都必须付出代价,C#由于垃圾回收而存在着非确定性性能损失的巨大成本。


2
在C++中,您可以使用不同的分配方法,因此根据在C#中如何分配内存(AOT?),可以以相同的方式(但速度更快)在C++中完成。 - zackery.fix
5
.NET在堆分配方面有一个有趣的优势,因为它只需要移动一个指针就能分配一个新对象。这仅仅得益于压缩垃圾回收器。当然,你也可以在C++中做同样的事情,但C++并没有这么做。有趣的是,你使用相同的论点来说"C#可以但不这么做,所以很差劲"和"C++没有,但它可以,所以很棒" :) - Luaan

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