C++性能与 Java/C#

2022-08-31 09:00:10

我的理解是,C/C++生成在特定机器架构上运行的本机代码。相反,像Java和C#这样的语言在虚拟机上运行,虚拟机抽象出原生架构。从逻辑上讲,由于这个中间步骤,Java或C#似乎不可能与C++的速度相匹配,但是我被告知最新的编译器(“热点”)可以达到甚至超过它。

也许这更像是一个编译器问题,而不是一个语言问题,但是任何人都可以用简单的英语解释这些虚拟机语言之一如何比母语表现得更好?


答案 1

JIT 与静态编译器

如前面的文章中所述,JIT 可以在运行时将 IL/字节码编译为本机代码。提到了这样做的代价,但没有得出结论:

JIT有一个巨大的问题是它不能编译所有内容:JIT编译需要时间,因此JIT将仅编译代码的某些部分,而静态编译器将生成完整的本机二进制文件:对于某种程序,静态编译器将很容易超过JIT。

当然,C#(或Java,或VB)通常比C++更快地产生可行且健壮的解决方案(如果只是因为C++具有复杂的语义,并且C++标准库虽然有趣且功能强大,但与.NET或Java标准库的全部范围相比,它非常差),因此通常,C++和.NET或Java JIT之间的差异对大多数用户来说都不可见, 对于那些关键的二进制文件,你仍然可以从C#或Java调用C++处理(即使这种原生调用本身可能非常昂贵)...

C++元编程

请注意,通常,您将C++运行时代码与其在 C# 或 Java 中的等效代码进行比较。但是C++有一个功能可以比Java / C#开箱即用,那就是模板元编程:代码处理将在编译时完成(因此,大大增加了编译时间),导致零(或几乎为零)运行时。

我还没有看到现实生活中的影响(我只玩概念,但到那时,差异是JIT的执行秒数,C++的执行时间为零),但这值得一提,以及模板元编程的事实并非微不足道......

Edit 2011-06-10:在C++中,玩弄类型是在编译时完成的,这意味着生成调用非泛型代码的泛型代码(例如,从字符串到类型T的泛型解析器,为它识别的类型T调用标准库API,并使解析器易于被其用户扩展)是非常容易和非常有效的,而Java或C#中的等效代码最好是痛苦的, 并且总是会更慢,即使在编译时知道类型,也会在运行时解析,这意味着你唯一 的希望是JIT内联整个事情。

...

Edit 2011-09-20:Blitz++(主页维基百科)背后的团队就是这样做的,显然,他们的目标是通过C++模板元编程,尽可能多地从运行时执行转移到编译时间,从而达到FORTRAN在科学计算方面的表现。因此,我上面写的“我还没有看到现实生活对此的影响”部分显然确实存在于现实生活中。

本机C++内存使用情况

C++的内存使用量与 Java/C# 不同,因此具有不同的优点/缺陷。

无论JIT优化如何,没有什么比直接指针访问内存更快了(让我们暂时忽略处理器缓存等)。因此,如果您在内存中有连续的数据,则通过C++指针(即C指针...让我们给 Caesar 它应有的) 将比 Java/C# 中快一倍。C++都有RAII,这使得很多处理比C#甚至Java更容易。C++不需要限定其对象的存在范围。C++没有条款。这不是错误。usingfinally

:-)

尽管有类似 C# 的基元结构,C++“在堆栈上”对象在分配和销毁方面不会花费任何费用,并且不需要 GC 在独立线程中工作即可进行清理。

至于内存碎片,2008年的内存分配器不是1980年的旧内存分配器,通常与GC进行比较:C++分配不能在内存中移动,这是真的,但是,就像在Linux文件系统上一样:当碎片没有发生时,谁需要硬盘碎片整理?为正确的任务使用正确的分配器应该是C++开发人员工具包的一部分。现在,编写分配器并不容易,然后,我们大多数人都有更好的事情要做,对于大多数使用来说,RAII或GC已经足够好了。

编辑 2011-10-04:有关高效分配器的示例:在 Windows 平台上,自 Vista 起,默认情况下启用低碎片堆。对于以前的版本,可以通过调用 WinAPI 函数 HeapSetInformation 来激活 LFH。在其他操作系统上,提供了备用分配器(有关列表,请参见 https://secure.wikimedia.org/wikipedia/en/wiki/Malloc

现在,随着多核和多线程技术的兴起,内存模型变得越来越复杂。在这个领域,我想.NET有优势,而Java,我被告知,占据了上风。对于一些“裸机上”的黑客来说,很容易称赞他的“靠近机器”代码。但是现在,手工生成更好的汇编比让编译器完成其工作要困难得多。对于C++来说,自十年以来,编译器通常比黑客更好。对于C#和Java来说,这甚至更容易。

尽管如此,新标准C++0x将强加一个简单的内存模型来C++编译器,这将标准化(从而简化)C++中有效的多处理/并行/线程代码,并使编译器的优化更容易,更安全。但随后,我们将在几年内看到它的承诺是否得到兑现。

C++/CLI 与 C#/VB.NET

注意:在本节中,我谈论的是C++/CLI,即 .NET 托管C++,而不是本机C++。

上周,我接受了.NET优化的培训,发现静态编译器无论如何都非常重要。与JIT一样重要。

在 C++/CLI(或其祖先托管C++)中编译的相同代码可能比在 C# 中生成的相同代码(或 VB.NET,其编译器生成与 C# 相同的 IL)快几倍。

因为C++静态编译器比C#更好地生成已经优化的代码。

例如,.NET 中的函数内联仅限于字节码长度小于或等于 32 个字节的函数。因此,C# 中的某些代码将生成一个 40 字节的访问器,JIT 永远不会内联该访问器。C++/CLI 中的相同代码将生成一个 20 字节的访问器,该访问器将由 JIT 内联。

另一个示例是临时变量,它们只是由C++编译器编译掉,同时仍在 C# 编译器生成的 IL 中提及。C++静态编译优化将导致更少的代码,从而再次授权更积极的JIT优化。

人们推测,这是因为C++/CLI编译器从C++本机编译器的大量优化技术中获利。

结论

我爱C++。

但在我看来,C#或Java都是更好的选择。不是因为它们比C++快,而是因为当你把它们的质量加起来时,它们最终会比C++更有效率,需要更少的培训,并且拥有更完整的标准库。至于大多数程序,它们的速度差异(以某种方式)可以忽略不计......

编辑 (2011-06-06)

我在 C#/.NET 上的经验

我现在有5个月几乎独家的专业C#编码(这加起来我的简历已经充满了C++和Java,以及一点C++ / CLI)。

我玩过WinForms(哎呀...)和WCF(很酷!)和WPF(很酷!!!!通过 XAML 和原始 C#。WPF 是如此简单,我相信 Swing 无法与之相比),以及 C# 4.0。

结论是,虽然在C#/Java中生成代码比在C++中更容易/更快,但在C#中生成强大,安全和健壮的代码(在Java中甚至更难)比在C++中更难。原因比比皆是,但可以总结为:

  1. 泛型不如模板强大尝试编写有效的泛型解析方法(从字符串到T),或者C#中boost::lexical_cast的有效等效物来理解问题)
  2. RAII仍然无与伦比GC仍然可以泄漏(是的,我必须处理这个问题),并且只会处理内存。即使C#的使用也不是那么容易和强大,因为编写正确的Dispose实现是困难的。)
  3. C# readonly 和 Java final 没有 C++ 的 const 那么有用在没有大量工作的情况下,你无法在 C# 中公开只读复杂数据(例如节点树),而它是C++的内置功能。不可变数据是一个有趣的解决方案,但并非所有数据都可以变得不可变,所以到目前为止,它甚至还不够)。

因此,只要你想要一些有用的东西,C#仍然是一种令人愉快的语言,但是当你想要一些总是安全工作的东西时,C#仍然是一种令人沮丧的语言。

Java更令人沮丧,因为它存在与C#相同的问题,而且更多:由于缺乏C#关键字的等效性,我的一位非常熟练的同事花了太多时间确保其资源得到正确释放,而C++中的等效项很容易(使用析构函数和智能指针)。using

所以我想C#/Java的生产力提升对于大多数代码来说都是可见的......直到有一天,你需要代码尽可能完美。那一天,你会知道痛苦。(你不会相信我们的服务器和GUI应用程序的要求...

关于服务器端 Java 和 C++

我与服务器团队保持联系(我在他们中间工作了2年,然后回到GUI团队),在建筑物的另一边,我学到了一些有趣的东西。

去年,趋势是让Java服务器应用程序注定要取代旧的C++服务器应用程序,因为Java有很多框架/工具,并且易于维护,部署等。

...直到低延迟问题在过去几个月里抬起了丑陋的头。然后,无论我们熟练的Java团队如何进行优化,Java服务器应用程序都简单而干净地输掉了与旧的,没有真正优化C++服务器的比赛。

目前,决定是保留Java服务器以供通用,其中性能虽然仍然很重要,但不关心低延迟目标,并积极优化已经更快的C++服务器应用程序,以满足低延迟和超低延迟的需求。

结论

没有什么比预期的简单了。

Java,甚至更多的C#,都是很酷的语言,具有广泛的标准库和框架,您可以在其中快速编码,并很快获得结果。

但是,当您需要原始功能,强大而系统的优化,强大的编译器支持,强大的语言功能和绝对的安全性时,Java和C#使得您很难赢得最后缺失但关键的质量百分比,您需要保持领先于竞争对手。

就好像你需要的C#/Java开发人员比C++更少的时间和经验来生成平均质量的代码,但另一方面,当你需要优秀到完美的质量代码时,突然间更容易和更快地在C++获得正确的结果。

当然,这是我自己的看法,也许仅限于我们的特定需求。

但是,今天在GUI团队和服务器端团队中,情况仍然如此。

当然,如果发生新情况,我会更新这篇文章。

编辑 (2011-06-22)

“我们发现,在性能方面,C++以很大的优势获胜。但是,它也需要最广泛的调整工作,其中许多工作是在普通程序员无法获得的复杂程度上完成的。

[...]Java版本可能是最容易实现的,但最难分析性能。具体来说,垃圾回收的效果很复杂,很难调整。

来源:

编辑 (2011-09-20)

“Facebook的最新消息是'合理编写C++代码运行得很快',这突显了在优化PHP和Java代码方面所花费的巨大努力。矛盾的是,C++代码比其他语言更难编写,但高效的代码(用C++编写比用其他语言编写)要容易得多。"

Herb Sutter at //build/,引用Andrei Alexandrescu的话

来源:


答案 2

通常,C# 和 Java 可以同样快或更快,因为 JIT 编译器(一种在第一次执行 IL 时编译 IL 的编译器)可以进行C++编译程序无法进行的优化,因为它可以查询计算机。它可以确定机器是英特尔还是AMD;Pentium 4, Core Solo, or Core Duo;或者是否支持SSE4等。

C++程序必须事先编译,通常进行混合优化,以便它在所有机器上都能很好地运行,但不像单个配置(即处理器,指令集,其他硬件)那样优化。

此外,某些语言功能允许C#和Java中的编译器对代码做出假设,从而允许它优化某些部分,而这些部分对于C / C++编译器来说是不安全的。当您可以访问指针时,会有很多不安全的优化。

此外,Java 和 C# 可以比C++更有效地进行堆分配,因为垃圾回收器和代码之间的抽象层允许它一次执行所有堆压缩(一个相当昂贵的操作)。

现在我不能在下一点上代表Java,但我知道例如C#在知道方法的主体为空时实际上会删除方法和方法调用。它将在整个代码中使用这种逻辑。

因此,如您所见,某些 C# 或 Java 实现会更快的原因有很多。

现在说了这么多,可以在C++进行特定的优化,这将吹走你可以用C#做的任何事情,特别是在图形领域和任何时候你接近硬件。指针在这里创造奇迹。

因此,根据你写的内容,我会选择一个或另一个。但是,如果你正在编写一些不依赖于硬件的东西(驱动程序,视频游戏等),我不会担心C#的性能(同样不能谈论Java)。它会做得很好。

在Java方面,@Swati指出了一篇好文章:

https://www.ibm.com/developerworks/library/j-jtp09275


推荐