C ++性能与Java / C#


119

我的理解是C / C ++会生成在特定机器体系结构上运行的本机代码。相反,诸如Java和C#之类的语言在虚拟机之上运行,该虚拟机抽象出本机体系结构。从逻辑上讲,由于这一中间步骤,Java或C#似乎不可能达到C ++的速度,但是有人告诉我最新的编译器(“热点”)可以达到或什至超过此速度。

也许这更像是编译器问题,而不是语言问题,但是谁能用通俗的英语解释这些虚拟机语言中的一种比本地语言有更好的表现?


Java和C#可以根据运行时可用的代码根据应用程序的实际运行方式进行优化。例如,它可以内联共享库中的代码,该代码实际上可以在程序运行时更改,但仍然正确。
彼得·劳瑞

有些实际测量来检查这些答案读了很多非常片状理论之前:shootout.alioth.debian.org/u32/...
Justicle

Answers:


178

通常,C#和Java可以达到相同或更快的速度,因为JIT编译器(可以在第一次执行IL时对其进行编译的编译器)可以进行优化,而C ++编译的程序无法进行查询,因为它可以查询机器。它可以确定计算机是Intel还是AMD;奔腾4,Core Solo或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


您的推理是虚假的-C ++程序是为它们的目标体系结构构建的,它们不需要在运行时进行切换。
Justicle 2011年

3
@Justicle您的c ++编译器针对不同体系结构提供的最好的选择通常是x86,x64,ARM等。现在,您可以告诉它使用特定功能(例如SSE2),如果您幸运的话,如果该功能不可用,它甚至会生成一些备份代码,但这大约可以实现。当然,没有特殊化取决于缓存大小和其他内容。
Voo

4
请参见shootout.alioth.debian.org/u32/…,以了解该理论没有发生的示例。
Justicle 2011年

1
老实说,这是最糟糕的答案之一。它是如此没有根据,我可以将其反转。泛化太多,知识太多(优化空函数实际上只是冰山一角)。一种豪华的C ++编译器具有:时间。另一种奢侈:不执行检查。但请在stackoverflow.com/questions/145110/c-performance-vs-java-c/…中找到更多信息。
塞巴斯蒂安·马赫

1
@OrionAdrian好的,我们现在已经整整圈了……有关此理论的例子,请参见shootout.alioth.debian.org/u32/…。换句话说,告诉我们在做出模糊的投机性陈述之前,您的理论可以被证明是正确的
Justicle,2012年

197

JIT与静态编译器

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

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

当然,C#(或Java或VB)通常比C ++更快地产生可行且健壮的解决方案(如果仅仅是因为C ++具有复杂的语义,而C ++标准库虽然有趣且强大,但与完整版本相比却相当差劲)。 (.NET或Java标准库的范围),因此通常,大多数用户看不到C ++和.NET或Java JIT之间的差异,对于那些至关重要的二进制文件,您仍然可以调用C ++处理从C#或Java(即使这种本地调用本身可能会非常昂贵)...

C ++元编程

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

我到目前为止还没有看到现实的效果(我只玩过概念,但是那时,JIT的执行时间为几秒钟,C ++ 的执行时间为零),但这是值得一提的,同时模板元编程不是不重要的...

编辑2011-06-10:在C ++中,类型的编译是在编译时完成的,这意味着生成泛型代码,该泛型代码将调用非泛型代码(例如,从字符串到类型T的泛型解析器,为其识别的类型T调用标准库API,并使解析器易于用户扩展)非常容易且非常有效,而Java或C#中的等效语言充其量是令人费解的,并且即使在编译时知道类型,也总是会变慢并在运行时解析,这意味着您唯一的希望是JIT可以对整个过程进行内联。

...

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

本机C ++内存使用情况

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

不管JIT优化如何,直接访问内存的操作都不会很快(让我们暂时忽略处理器缓存等)。因此,如果您的内存中有连续的数据,那么通过C ++指针(即C指针……让Caesar给出应有的条件)对其进行访问将比Java / C#中的速度快得多。C ++具有RAII,这比C#甚至Java中的许多处理都容易得多。C ++不需要using限制其对象的存在范围。而且C ++没有finally子句。这不是错误。

:-)

尽管具有类似于C#基本结构的结构,但C ++“在堆栈上”对象在分配和销毁时将不花费任何费用,并且不需要GC就可以在独立线程中进行清理。

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

编辑2011-10-04:有关有效分配器的示例:在Windows平台上,自Vista开始,默认情况下启用低碎片堆。对于以前的版本,可以通过调用WinAPI函数HeapSetInformation来激活LFH 。在其他OS上,提供了备用分配器(请参见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,其编译器生成的IL与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#编码(这加起来我的CV已经充满了C ++和Java,以及一点C ++ / CLI)。

我玩过WinForms(Ahem ...),WCF(cool!)和WPF(Cool !!!!都通过XAML和原始C#玩过。WPF非常简单,我相信Swing不能与之相比)和C#4.0。

结论是,虽然在C#/ Java中生成的代码比在C ++中更容易/更快地生成,但是在C#中(比Java更加难)在Java中生成更强大,安全和健壮的代码要困难得多。原因很多,但可以归纳为:

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

因此,只要您想要某种有效的东西,C#仍然是一种令人愉悦的语言,但是当您想要一种始终安全地起作用的东西时,C#就令人沮丧。

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

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

关于服务器端Java和C ++

我在大楼的另一侧与服务器团队保持联系(在返回GUI团队之前,我在其中工作了2年),我学到了一些有趣的东西。

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

...直到低延迟的问题在过去的几个月里抬起了头。然后,无论我们熟练的Java团队尝试进行何种优化,Java服务器应用程序都简单而干净地失去了与未经过优化的旧C ++服务器的竞争。

当前,决定是将Java服务器保留在性能仍然很重要但不受低延迟目标关注的地方的通用服务器,并针对低延迟和超低延迟需求积极优化本已更快的C ++服务器应用程序。

结论

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

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

但是,当您需要原始能力,强大而系统的优化,强大的编译器支持,强大的语言功能以及绝对的安全性时,Java和C#很难赢得您需要保持在竞争之上的最后缺失但至关重要的质量百分比。

似乎与使用C ++相比,用C#/ Java花费更少的时间和更少的经验的开发人员来生成平均质量的代码,但是,另一方面,当您需要优秀的,完美的质量代码时,突然变得更容易,更快地得到结果。就在C ++中

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

但是,无论今天在GUI团队还是在服务器端团队中,这都是今天发生的事情。

当然,如果发生新情况,我将更新此帖子。

编辑(2011-06-22)

“我们发现,就性能而言,C ++大获全胜。但是,它也需要最广泛的调整工作,其中许多工作都是在复杂的水平上完成的,而普通的程序员则无法使用。

[...] Java版本可能是最简单的实现,但最难分析其性能。具体来说,垃圾回收的影响非常复杂,很难调整。”

资料来源:

编辑(2011-09-20)

“ Facebook上流行的说法是,' 合理编写的C ++代码运行起来很快 ',这突显了优化PHP和Java代码所花费的巨大努力。自相矛盾的是,C ++代码比其他语言更难编写,但是高效的代码是(用C ++编写比用其他语言编写要容易得多)。

- 香草萨特//建立/,报价安德烈Alexandrescu的

资料来源:


8
您在5个月的C#后完全按照我的经验进行了编辑(模板更好,const更好,RAII)。+1。这三个仍然是我个人对于C ++的杀手级功能(或D,我还没来得及)。
塞巴斯蒂安·马赫

“代码处理将在编译时完成”。因此,模板元编程仅在程序中才可用,而在编译时通常不是这种情况,例如,不可能在香草C ++中编写具有竞争力的正则表达式库,因为它无法运行时代码生成(元编程)。
JD 2012年

“使用类型进行播放是在编译时完成的……Java或C#中的等效项充其量很难编写,并且即使在编译时知道类型,也总是会变慢并在运行时解决”。在C#中,这仅适用于引用类型,而不适用于值类型。
JD 2012年

1
“无论JIT优化如何,直接指针访问内存都不会很快...如果内存中有连续数据,则通过C ++指针(即C指针...让Caesar承担应有的责任)访问它会倍增。比Java / C#更快”。人们已经观察到Java在SciMark2基准测试的SOR测试中击败了C ++,正是因为指针阻碍了与别名相关的优化。blogs.oracle.com/dagastine/entry/sun_java_is_faster_than
JD

同样值得注意的是,.NET在链接后确实在动态链接的库之间进行泛型的类型特殊化,而C ++则不能,因为必须在链接之前解析模板。显然,泛型相对于模板的最大优势是可理解的错误消息。
JD 2012年

48

每当我谈到托管性能与非托管性能时,我都想指出Rico(和Raymond)所做的系列比较中/英文字典的C ++和C#版本。这个Google搜索会让您自己阅读,但是我喜欢Rico的摘要。

那么,我为自己惨败而感到羞愧吗?几乎不。几乎无需付出任何努力,托管代码就获得了很好的结果。为了击败托管的雷蒙德,必须:

  • 编写自己的文件I / O内容
  • 编写自己的字符串类
  • 编写自己的分配器
  • 撰写自己的国际地图

当然,他使用可用的低级库来执行此操作,但这仍然是很多工作。您能称呼剩下的STL程序吗?我不这么认为,我认为他保留了std :: vector类,该类最终从不成问题,并且保留了find函数。几乎所有其他一切都消失了。

因此,是的,您绝对可以击败CLR。我认为Raymond可以使他的程序运行得更快。

有趣的是,两个程序内部计时器报告的解析文件的时间大致相同-每个计时器30毫秒。区别在于开销。

对我来说,最重要的是,非托管版本花了6个修订版才击败了托管版本,后者是原始非托管代码的简单移植。如果您需要性能的最后一点(并且有时间和专业知识来获得它),则必须不受管理,但是对我而言,我将采用第一个版本在33之上的数量级优势。如果尝试6次,我会有所收获。


3
链接已死,在此处找到提到的文章:blogs.msdn.com/b/ricom/archive/2005/05/10/416151.aspx
gjvdkamp 2011年

首先,如果我们看一下Raymond Chen的代码,他显然不太了解C ++或数据结构。即使在C代码没有性能优势的情况下,他的代码也几乎可以直达低级C代码(这似乎是一种不信任,并且可能缺乏如何使用探查器的知识)。他也未能理解实现字典的算法上最合理的方式(为基督的缘故,他使用了std :: find)。如果关于Java,Python,C#等有什么好处-它们都提供了非常有效的词典...
stinky472 2012年

尝试甚至使用std :: map对C ++甚至是哈希表都更有利。最后,字典正是从高级库和框架中受益最大的程序类型。它没有像所涉及的库那样显示出语言上的差异(我很高兴地说C#更加完善,并且提供了更多适合该任务的工具)。展示一个程序,该程序可以比较大型的内存块,例如大型矩阵/矢量代码。即使在这种情况下,编码人员也不知道该怎么办,这也将很快解决……
stinky472 2012年

26

用于特定CPU优化的编译通常被高估了。只需使用C ++编写一个程序,然后针对pentium PRO进行优化编译并在奔腾4上运行。然后针对pentium 4进行优化进行重新编译。一般结果??通常性能提升不到2-3%。因此,JIT的理论优势几乎没有。大多数性能差异仅在使用标量数据处理功能时才能观察到,而最终需要手动微调以达到最佳性能。这种优化的执行速度缓慢且成本高昂,因此有时仍然不适合JIT。

在现实世界和实际应用程序中,C ++通常仍比Java快,这主要是因为内存占用量更少,从而导致更好的缓存性能。

但是要使用所有C ++功能,开发人员必须努力工作。您可以取得优异的成绩,但是您必须为此动脑筋。C ++是一种决定为您提供更多工具的语言,收取一定的费用,您必须学习这些工具才能很好地使用该语言。


4
您正在编译的不是用于CPU优化,而是正在编译的用于运行时路径优化。如果发现使用特定参数经常调用某个方法,则可以将该参数作为常量预编译该例程,该常量可以(在控制流量的布尔值的情况下)分解出巨大的工作量。C ++无法接近进行这种优化。
比尔K

1
那么,JIT如何在重新编译例程中利用观察到的运行路径,这有什么不同?
David Thornley,2009年

2
@Bill我可能会混合两件事...但是在运行时在指令管道中完成分支预测不是实现独立于语言的相似目标吗?
哈迪2010年

@Hardy是的,CPU可以执行分支预测,而与语言无关,但是它不能通过观察循环对任何事物都没有影响来排除整个循环。也不会观察到mult(0)硬连接返回0,而只是将整个方法调用替换为if(param == 0)result = 0;。并避免整个函数/方法调用。如果编译器对所发生的事情有一个全面的了解,那么C可以完成这些事情,但是通常在编译时它没有足够的信息。
Bill K

21

JIT(及时编译)的速度非常快,因为它针对目标平台进行了优化。

这意味着,无论开发人员在哪个CPU上编写代码,它都可以利用您的CPU支持的任何编译器技巧。

.NET JIT的基本概念如下所示(大大简化):

首次调用方法:

  • 您的程序代码调用方法Foo()
  • CLR查看实现Foo()的类型并获取与其关联的元数据
  • 通过元数据,CLR知道IL(中间字节代码)存储在哪个内存地址中。
  • CLR分配一块内存,并调用JIT。
  • JIT将IL编译为本地代码,将其放入分配的内存中,然后更改Foo()的类型元数据中的函数指针以指向此本地代码。
  • 本机代码已运行。

第二次调用方法:

  • 您的程序代码调用方法Foo()
  • CLR查看实现Foo()的类型,并在元数据中找到函数指针。
  • 在此内存位置的本机代码已运行。

正如您所看到的,第二次是它与C ++几乎相同的过程,只是具有实时优化的优势。

就是说,还有其他开销问题会减慢托管语言的速度,但是JIT很有帮助。


顺便说一下,乔纳森(Jonathan),我认为有人仍在对你的事情投反对票。当我投票赞成您时,您在此职位上的评分为-1。
Brian R. Bondy

12

我喜欢Orion Adrian的答案,但它还有另一方面。

几十年前,关于汇编语言与“人类”语言(如FORTRAN)提出了相同的问题。答案的一部分是相似的。

是的,在任何给定的(非平凡的)算法上,C ++程序都能够比C#快,但是C#中的程序通常会比C ++中的“天真”实现以及C ++中的优化版本快或快。开发将花费更长的时间,并且可能仍会以很小的优势击败C#版本。那么,真的值得吗?

您必须一对一地回答该问题。

就是说,我一直是C ++的忠实拥护者,而且我认为它是一种令人难以置信的表达和强大的语言-有时被人们低估。但是在许多“现实生活”问题中(对我个人而言,这意味着“获得报酬来解决的那种”),C#将使工作更快,更安全地完成。

您付出的最大罚款?许多.NET和Java程序都是内存消耗。我已经看到.NET和Java应用程序占用“数百”兆字节的内存,而类似复杂性的C ++程序几乎不占用“数十”兆字节。


7

我不确定即使使用Hotspot,您会发现Java代码运行的频率也比C ++快的多,但是我会花一些时间解释它是如何发生的。

将已编译的Java代码视为JVM的解释机语言。当Hotspot处理器注意到某些已编译的代码将被多次使用时,它将对机器代码进行优化。由于手工调整大会几乎总是快于C ++编译的代码,这是确定的数字,编程,调整机器代码不会是糟糕。

因此,对于高度重复的代码,我可以看到Hotspot JVM可以比C ++更快地运行Java ...直到垃圾回收开始发挥作用。:)


您可以扩展断言Since hand-tuning Assembly is almost always faster than C++ compiled code吗?“手动调整程序集”和“ C ++编译代码”是什么意思?
paercebal 2011年

好吧,它基于这样的思想,即编译器的优化器遵循规则,而编码人员则不遵循规则。因此,总会有代码使优化器发现它无法完美地优化,而人类却可以通过查看更大的图片或更多地了解代码的实际作用来实现。我要补充一点,这是3年的评论,而且我对HotSpot的了解比以前更多,而且我可以很容易地看到动态优化是使代码更快运行的一种非常好的方法。
billjamesdev 2011年

1. Hotspot或任何其他JIT的优化仍然是编译器优化。与静态编译器相比,JIT的优势在于可以内联某些结果(经常调用的代码),甚至可以基于执行处理器进行优化,但它仍然是编译器优化。。。2.我猜您是在谈论算法优化,而不是“装配微调”。十多年来,“由人工编码器进行手动汇编微调”未能产生比编译器优化更好的结果。实际上,玩装配的人通常会取消任何优化...
paercebal 2011年

好的,我知道我使用了错误的术语,“编译器优化”而不是“静态优化”。我要指出的是,至少在游戏行业中,就PS2而言,我们仍在某些地方使用手工编码的组件来“优化”我们在游戏机上已知的特定芯片。这些新芯片的交叉编译器还不如x86架构的交叉编译器复杂。回到上面的原始问题:JIT的优势在于能够在优化之前进行测量,这是一件好事
billjamesdev 2011年

请注意,大多数生产GC也使用手写汇编程序,因为C / C ++不会削减它。
JD

6

通常,与语言相比,程序的算法对于应用程序的速度将更为重要。您可以使用任何语言(包括C ++)来实现较差的算法。考虑到这一点,通常您将能够使用一种可以帮助您实现更高效算法的语言来更快地编写运行代码。

高级语言通过提供对许多有效的预构建数据结构的更轻松访问以及鼓励可帮助您避免低效率代码的实践,在此方面做得很好。当然,有时它们也可以使编写一堆非常慢的代码变得容易,因此您仍然必须了解您的平台。

同样,C ++正在赶上STL容器,自动指针等“新”(请注意引号)功能-例如,参见boost库。您可能偶尔会发现,完成某些任务的最快方法需要高级语言中禁止的指针算术之类的技术,尽管它们通常允许您调出使用可以按需实现的语言编写的库。

最主要的是要知道您使用的语言,与之关联的API,它可以做什么以及它的局限性。



4

这是另一个有趣的基准,您可以在自己的计算机上尝试一下。

它比较了ASM,VC ++,C#,Silverlight,Java applet,Javascript,Flash(AS3)

Roozz插件速度演示

请注意,JavaScript的速度变化很大,具体取决于执行该浏览器的浏览器。Flash和Silverlight也是如此,因为这些插件与托管浏览器的运行过程相同。但是Roozz插件运行标准的.exe文件,这些文件以自己的进程运行,因此速度不受托管浏览器的影响。


4

您应该定义“性能优于..”。好吧,我知道,您问的是速度,但它并不是至关重要的。

  • 虚拟机是否执行更多的运行时开销?是!
  • 他们会吃更多的工作记忆吗?是!
  • 它们是否具有较高的启动成本(运行时初始化和JIT编译器)?是!
  • 他们需要安装巨大的库吗?是!

依此类推,这是有偏见的,是;)

使用C#和Java,您需要付出一定的代价(更快的编码,自动内存管理,大型库等)。但是您没有太多余地来讨价还价:拿完整的包裹或者什么都不做。

即使那些语言可以优化某些代码以使其比编译后的代码执行得更快,但整个方法还是无效的。想象一下,每天用卡车开车到您的工作场所5英里!它舒适,感觉良好,很安全(极端的压溃区),踩油门一段时间后,它的速度甚至可以和标准车一样快!为什么我们所有人都没有卡车开车上班?;)

在C ++中,您得到的是所支付的,而不是更多,更少。

引用Bjarne Stroustrup的话:“ C ++是我最喜欢的垃圾收集语言,因为它产生的垃圾很少” 链接文本


好吧,我认为他对缺点有个好主意,他还说:“ C使脚部射击容易; C ++使其更难,但是当您这样做时,
会把

我相信“他们是否需要安装巨大的库”,Java正在使用项目拼图解决此问题。
toc777 2011年

“在C ++中,您得到的是所付的,而不是更多,不少”。反例:我对OCaml和C ++(GNU GCC)中的RB树实现进行了基准测试,如果已经存在要添加的元素以重用现有集合,则该异常使用一个异常长时间从递归中退出。OCaml的速度比C ++快6倍,因为在堆栈未展开时,OCaml不需要为检查析构函数而付费。
JD

3
@Jon:但是在某个(稍后?)时间点,它仍然必须销毁对象(至少必须释放其内存)。还要注意,例外是针对例外情况的,至少在C ++中,应遵守该规则。当发生异常时,C ++异常可能很重,这是一个折衷方案。
Frunsi 2011年

@Jon:也许尝试times在shell上重复您的基准测试。这样就可以检查整个程序,而不仅仅是一个方面。结果是否相似?
Frunsi 2011年

3

从Java或C#编译器生成的可执行代码不会被解释-会“及时”(JIT)编译为本地代码。因此,在执行过程中遇到Java / C#程序中的首次代码时,由于“运行时编译器”(又名JIT编译器)将字节码(Java)或IL代码(C#)转换为本机机器指令,因此会产生一些开销。但是,下次在应用程序仍在运行时遇到该代码时,将立即执行本机代码。这就解释了某些Java / C#程序最初看起来很慢,但是运行时间越长,性能越好。一个很好的例子是ASP.Net网站。第一次访问该网站时,由于JIT编译器将C#代码编译为本机代码,因此速度可能会慢一些。


3

关于您所问的特定问题,这里有一些很好的答案。我想退后一步,看看大图。

请记住,用户对编写软件速度的看法受许多其他因素的影响,而不仅仅是代码生成的优化程度。这里有些例子:

  • 手动内存管理很难正确执行(没有泄漏),甚至更难有效地执行(完成后不久释放内存)。通常,使用GC更有可能产生可以很好地管理内存的程序。您是否愿意努力工作,并延迟交付软件,以超越GC?

  • 我的C#比我的C ++更易于阅读和理解。我还有更多方法可以使自己确信我的C#代码可以正常工作。这意味着我可以优化算法,减少引入错误的风险(并且用户不喜欢崩溃的软件,即使崩溃很快!)

  • 与使用C ++相比,在C#中创建软件的速度更快。这样可以腾出时间来提高性能,同时仍然可以按时交付我的软件。

  • 用C#编写良好的UI比使用C ++容易,因此我更有可能在UI保持响应的同时将工作推送到后台,或者在程序不得不阻塞一会儿时提供进度或听到的UI。这不会使任何事情变得更快,但是会使用户对等待更加满意。

我说的关于C#的一切可能对Java都是正确的,只是我没有经验可以肯定地说。


3

如果您是一位学习C ++的Java / C#程序员,那么您会很想继续使用Java / C#进行思考,并将逐字转换为C ++语法。在这种情况下,您只会得到前面提到的本机代码与解释型/ JIT相比的好处。为了在C ++与Java / C#中获得最大的性能提升,您必须学习在C ++中进行思考,并专门设计代码以利用C ++的优势。

释义Edsger Dijkstra:[您的第一语言]残缺了无法康复的思想。
杰夫·阿特伍德(Jeff Atwood)来解释:您可以用任何新语言写[您的第一语言]。


1
我怀疑“您可以用任何语言编写FORTRAN”这句话早于Jeff的职业生涯。
David Thornley,2009年

3

最重要的JIT优化之一是方法内联。如果Java能保证运行时正确性,它甚至可以内联虚拟方法。这种优化通常不能由标准的静态编译器执行,因为它需要对整个程序进行分析,而由于要进行单独的编译,因此很难(与之相反,JIT拥有所有可用的程序)。方法内联改进了其他优化,从而提供了更大的代码块进行优化。

Java / C#中的标准内存分配也更快,并且释放(GC)不会慢很多,但是确定性却很少。


请注意,freedelete都不是确定性的,可以通过不分配来使GC确定性。
JD

3

虚拟机语言不太可能胜过编译语言,但它们可以足够接近而无所谓,至少出于以下原因(由于从未使用过C#,我在这里使用Java是因为)。

1 / Java运行时环境通常能够检测经常运行的代码段,并对这些部分执行即时(JIT)编译,以便将来以完整的编译速度运行。

2 / Java库的大部分已编译,因此,当您调用库函数时,您正在执行的是编译后的代码,而不是解释代码。您可以通过下载OpenJDK来查看代码(用C语言编写)。

3 /除非您要进行大量计算,否则程序在大多数时间都在运行,它会等待非常慢的(相对而言)人的输入。

4 /由于在加载类时对Java字节码进行了很多验证,因此大大减少了运行时检查的常规开销。

5 /在最坏的情况下,可以将性能密集的代码提取到已编译的模块中,然后从Java中调用(请参阅JNI),以使其全速运行。

总而言之,Java字节码永远不会超过本地机器语言,但是有一些方法可以减轻这种情况。Java(如我所见)的最大优势是巨大的标准库和跨平台的特性。


1
关于第2项,“对Java库的2 /大部分进行了编译,以便在调用库函数时,您正在执行的是已编译的代码,而不是被解释的”:您对此有何引用?如果确实如您所描述,我希望调试器会遇到很多本机代码,但我不会。
cero

回复:cero调试器通常使用效率较低但更具表达性的路径,因此对于任何与性能相关的问题都不是一个很好的标记。
古凡特

2
这个HUGH库还有另一个巨大的性能提升-库代码的编写可能比许多程序员自己写的(由于时间有限,缺乏专业知识)和Java编写的要好,因为许多原因,程序员经常使用图书馆。
Liran Orevi

3

Orion Adrian,让我倒转您的文章,看看您的言论有多无根据,因为关于C ++的说法也很多。并告诉Java / C#编译器优化空函数确实会让您听起来好像不是我的优化专家,因为a)为什么真正的程序应该包含空函数,除了非常糟糕的旧版代码之外,b)确实不是黑色和出血边缘优化。

除了那句话之外,您还公然地使用了指针,但是Java和C#中的对象基本上不像C ++指针那样工作吗?它们可以不重叠吗?他们可以不为空吗?C(以及大多数C ++实现)具有limit关键字,都具有值类型,C ++具有非空保证的引用值。Java和C#提供什么?

>>>>>>>>>>

通常,C和C ++可以一样快或更快,因为AOT编译器(可以在部署之前一劳永逸地在许多核心构建服务器的高内存上编译您的代码的编译器)可以对C#编译程序进行优化不能,因为有很多时间可以这样做。编译器可以确定该计算机是Intel还是AMD;否则,请执行以下操作。奔腾4,Core Solo或Core Duo;或者是否支持SSE4等,并且如果您的编译器不支持运行时调度,则可以通过部署一些专用二进制文件自己解决问题。

AC#程序通常是在运行时进行编译的,因此它可以在所有机器上正常运行,但是没有针对单个配置(即处理器,指令集,其他硬件)进行尽可能多的优化,并且必须花费一些时间第一。诸如循环裂变,循环反转,自动矢量化,整个程序优化,模板扩展,IPO等功能非常难以完全解决,而不会干扰最终用户。

此外,某些语言功能允许C ++或C编译器对您的代码进行假设,从而使它们可以优化某些部分,而这对于Java / C#编译器来说是不安全的。当您无法访问泛型的完整类型ID或有保证的程序流时,有很多优化是不安全的。

同样,C ++和C只需一次增加寄存器就可以一次完成许多堆栈分配,这肯定比Javas和C#分配在垃圾收集器和您的代码之间的抽象层上效率更高。

现在,我不能再说Java了,但是我知道,例如C ++编译器在知道方法主体为空时实际上将删除方法和方法调用,它将消除常见的子表达式,可以尝试重试为了找到最佳的寄存器用法,它不执行边界检查,它将对循环和内部循环进行自动向量化,并且将内到外反转,将条件移出循环,将循环拆分和不拆分。它将按照C方式将std :: vector扩展为本机零开销数组。它将进行程序间优化。它将直接在调用者站点上构造返回值。它将折叠并传播表达式。它将以缓存友好的方式将数据重新排序。它将执行跳转线程。它使您可以以零的运行时间开销编写编译时射线跟踪器。它将进行非常昂贵的基于图的优化。如果它将某些代码替换为语法上完全不相等但在语义上等效的代码,则它将降低强度(旧的“ xor foo,foo”只是最简单的,尽管此类过时的优化)。如果您有疑问,可以省略IEEE浮点标准,并启用更多的优化功能,例如浮点操作数重新排序。在对代码进行按摩和破坏之后,它可能会重复整个过程,因为通常,某些优化为甚至某些优化奠定了基础。它也可能只是使用改组后的参数重试,并查看其他变体在其内部排名中的得分。并且它将在您的代码中使用这种逻辑。是否用语法上完全不相等但语义上等效的代码替换了某些代码(旧的“ xor foo,foo”只是最简单的,但这种类型的过时优化)。如果您有疑问,可以省略IEEE浮点标准,并启用更多的优化功能,例如浮点操作数重新排序。在对代码进行按摩和破坏之后,它可能会重复整个过程,因为通常,某些优化为甚至某些优化奠定了基础。它也可能只是使用改组后的参数重试,并查看其他变体在其内部排名中的得分。并且它将在您的代码中使用这种逻辑。是否用语法上完全不相等但语义上等效的代码替换了某些代码(旧的“ xor foo,foo”只是最简单的,但这种类型的过时优化)。如果您有疑问,可以省略IEEE浮点标准,并启用更多的优化功能,例如浮点操作数重新排序。在对代码进行按摩和破坏之后,它可能会重复整个过程,因为通常,某些优化为甚至某些优化奠定了基础。它也可能只是使用改组后的参数重试,并查看其他变体在其内部排名中的得分。并且它将在您的代码中使用这种逻辑。如果您有疑问,可以省略IEEE浮点标准,并启用更多的优化功能,例如浮点操作数重新排序。在对代码进行按摩和破坏之后,它可能会重复整个过程,因为通常,某些优化为甚至某些优化奠定了基础。它也可能只是使用改组后的参数重试,并查看其他变体在其内部排名中的得分。并且它将在您的代码中使用这种逻辑。如果您有疑问,可以省略IEEE浮点标准,并启用更多的优化功能,例如浮点操作数重新排序。在对代码进行按摩和破坏之后,它可能会重复整个过程,因为通常,某些优化为甚至某些优化奠定了基础。它还可能只是使用改组后的参数重试,并查看其他变体在其内部排名中的得分。并且它将在整个代码中使用这种逻辑。它也可能只是使用改组后的参数重试,并查看其他变体在其内部排名中的得分。并且它将在您的代码中使用这种逻辑。它还可能只是使用改组后的参数重试,并查看其他变体在其内部排名中的得分。并且它将在整个代码中使用这种逻辑。

因此,正如您所看到的,某些C ++或C实现会更快的原因很多。

综上所述,可以使用C ++进行许多优化,这些优化将使您无法使用C#进行的所有工作,特别是在数字运算,实时和接近金属领域,但不仅限于此。您甚至不必触摸单个指针就可以走很长一段路。

因此,根据您所写的内容,我会选择其中之一。但是,如果您编写的内容与硬件无关(驱动程序,视频游戏等),那么我就不必担心C#的性能(再也不能说Java)。会很好的。

<<<<<<<<<<<

通常,某些通用论点在特定帖子中听起来很酷,但通常听起来并不可信。

无论如何,要和平:AOTJIT都很棒。唯一正确的答案可以是:这取决于。真正的聪明人知道,无论如何,您都可以利用两全其美的方法。


2

如果Java解释器生产,实际上是机器代码,它只会发生更好的比机器代码的编译器生成你正在写的C ++代码的优化,到所在的C ++代码比Java和解释成本慢点。

但是,实际上发生这种情况的几率很低-除非Java拥有一个编写得很好的库,并且您有自己编写得不好的C ++库。


我还相信,在较低的级别上以较少的抽象性进行工作时,您也将开发出更快的程序,这也具有一定的语言权重。这与字节码执行本身的要点无关。
Brian R. Bondy

2

实际上,C#并不像Java那样在虚拟机中真正运行。IL被编译为汇编语言,该语言完全是本机代码,并且以与本机代码相同的速度运行。您可以对.NET应用程序进行预JIT,这将完全消除JIT成本,然后您将运行完全本机代码。

.NET变慢的原因不是因为.NET代码更慢,而是因为它在后台执行了很多工作,如垃圾收集,检查引用,存储完整的堆栈框架等。这在执行时会非常强大且有用建立应用程序,但也要付出代价。请注意,您也可以在C ++程序中完成所有这些操作(许多核心.NET功能实际上是.NET代码,您可以在ROTOR中查看)。但是,如果您手动编写相同的功能,由于.NET运行时已经过优化和微调,因此最终程序可能会慢得多。

就是说,托管代码的优势之一是它可以完全验证。您可以在执行代码之前验证该代码永远不会访问其他进程的内存或对它们进行不明智的操作。微软拥有一个完全托管操作系统的研究原型,令人惊讶地表明,利用此验证功能来关闭托管程序不再需要的安全功能,一个100%托管环境实际上可以比任何现代操作系统更快地运行。 (在某些情况下,我们说的是10倍)。SE电台在谈论这个项目时有一段精彩的插曲。


1

在某些情况下,托管代码实际上可以比本机代码更快。例如,“标记清除”垃圾收集算法允许JRE或CLR之类的环境在一次通过中释放大量短期(通常)的对象,其中大多数C / C ++堆对象是一次释放的。一次。

来自维基百科

出于许多实际目的,用垃圾回收语言实现的分配/取消分配密集型算法实际上比使用手动堆分配的等效算法更快。这样做的主要原因是,垃圾收集器允许运行时系统以潜在的有利方式摊销分配和释放操作。

也就是说,我编写了很多C#和很多C ++,并且运行了很多基准测试。以我的经验,C ++在两种方面比C#快得多:(1)如果您使用用C#编写的某些代码,请将其移植到C ++,本机代码往往会更快。快多少?嗯,它变化很大,但是速度提高100%并不少见。(2)在某些情况下,垃圾回收会大大降低托管应用程序的速度。.NET CLR的大堆(例如,> 2GB)做得很糟糕,并且最终可能会花费大量时间在GC上,甚至在具有很少甚至没有中间寿命对象的应用程序中也是如此。

当然,在我遇到的大多数情况下,托管语言足够快,而且远见卓识,而为了获得C ++的额外性能而在维护和编码方面的权衡根本不是一个好方法。


1
问题是对于长时间运行的进程(例如Web服务器),随着时间的流逝,您的内存将变得碎片化(在C ++编写的程序中),您将必须实现类似于垃圾回收的内容(或经常重新启动,请参见IIS)。 )。
Tony BenBrahim

3
我还没有观察到要永久运行的大型Unix程序。它们倾向于用C编写,这在内存管理方面比C ++更糟糕。
David Thornley,2009年

当然,问题是我们是在比较托管代码还是非托管代码的程序实现,还是该语言的理论最高性能。显然,非托管代码始终至少可以与托管代码一样快,因为在最坏的情况下,您只能编写一个与托管代码完全一样的非托管程序!但是大多数性能问题是算法问题,而不是微观问题。此外,您不会以相同的方式优化托管和非托管代码,因此“ C#中的C ++”通常无法正常工作。
kyoryu

2
在C / C ++中,您可以在堆栈上分配短暂的对象,并在适当的时候进行操作。在托管代码中,你不能,你别无选择。同样,在C / C ++中,您可以在连续区域(新Foo [100])中分配对象列表,而在托管代码中则不能。因此,您的比较无效。好吧,这种选择的力量给开发人员带来了负担,但是通过这种方式,他们学会了了解自己所生活的世界(记忆……)。
弗隆西(Frunsi)

@frunsi:“在C / C ++中,您可以在连续区域(新Foo [100])中分配对象列表,而在托管代码中则不能”。那是不对的。局部值类型是堆栈分配的,您甚至可以在C#中堆栈分配它们的数组。甚至有一些用C#编写的生产系统在稳定状态下完全没有分配。
JD


1

实际上,Sun的HotSpot JVM使用“混合模式”执行。它解释该方法的字节码,直到确定(通常通过某种计数器)某个特定的代码块(方法,循环,try-catch块等)将要执行很多,然后JIT对其进行编译。JIT编译方法所需的时间通常比该方法很少运行时要花费的时间要长。对于“混合模式”来说,性能通常会更高,因为JVM不会浪费时间来很少(如果有的话)运行JITing代码。C#和.NET不这样做。.NET JIT经常浪费时间。


1

阅读有关HP Labs的Dynamo的信息Dynamo是PA-8000的解释程序,它可以在PA-8000上运行,并且运行程序的速度通常比本地程序快。那就一点也不奇怪了!

不要将其视为“中间步骤”-运行程序已经涉及许多其他步骤,无论使用哪种语言。

通常可以归结为:

  • 程序具有热点,因此,即使您在运行95%的代码主体时运行得较慢,但在5%的情况下运行得更快,仍然可以在性能上保持竞争力

  • HLL比C / C ++等LLL更了解您的意图,因此可以生成更优化的代码(OCaml甚至更多,并且在实践中通常更快)

  • JIT编译器具有很多静态编译器没有的信息(例如,您这次恰好拥有的实际数据)

  • JIT编译器可以在运行时进行传统链接器实际上不允许执行的优化(例如对分支进行重新排序,以使常见情况变得平缓,或者内联库调用)

总而言之,C / C ++的性能很差劲:关于您的数据类型的信息很少,没有关于数据的信息,也没有动态运行时,从而无法进行很多运行时优化。


1

当Java或CLR的速度比C ++快时,您可能会突然爆发,但总体性能在应用程序的生命周期中会更差:有关某些结果,请参见www.codeproject.com/KB/dotnet/RuntimePerformance.aspx。



1

我的理解是C / C ++会生成在特定机器体系结构上运行的本机代码。相反,诸如Java和C#之类的语言在虚拟机之上运行,该虚拟机抽象出本机体系结构。从逻辑上讲,由于这一中间步骤,Java或C#似乎不可能达到C ++的速度,但是有人告诉我最新的编译器(“热点”)可以达到或什至超过此速度。

那是不合逻辑的。使用中间表示不会固有地降低性能。例如,llvm-gcc通过LLVM IR(这是一个虚拟的无限寄存器计算机)将C和C ++编译为本地代码,并且可以实现出色的性能(经常击败GCC)。

也许这更像是编译器问题,而不是语言问题,但是谁能用通俗的英语解释这些虚拟机语言中的一种比本地语言有更好的表现?

这里有些例子:

  • 具有JIT编译功能的虚拟机有助于运行时代码生成(例如,System.Reflection.Emit在.NET上),因此您可以使用C#和F#等语言即时编译生成的代码,但必须求助于用C或C ++编写相对较慢的解释器。例如,实现正则表达式。

  • 虚拟机的某些部分(例如,写屏障和分配器)通常用手工编码的汇编器编写,因为C和C ++不能生成足够快的代码。如果程序对系统的这些部分施加压力,那么可以想象它会胜过可以用C或C ++编写的任何内容。

  • 动态链接本机代码需要符合ABI规范,这可能会妨碍性能并避免整个程序的优化,而链接通常推迟到VM上进行,并且可以受益于整个程序的优化(例如.NET的通用泛型)。

我还想解决上述paercebal极力支持的答案的某些问题(因为有人不断删除我对他的答案的评论),这种观点产生了适得其反的两极化观点:

代码处理将在编译时完成...

因此,模板元编程仅在程序在编译时可用的情况下才有效(通常不是这种情况),例如,不可能在香草C ++中编写具有竞争力的正则表达式库,因为它无法运行时代码生成(元编程)。

...使用类型进行播放是在编译时完成的... Java或C#中的等效项充其量很难编写,并且即使在编译时知道类型,也总是较慢并在运行时解析。

在C#中,这仅适用于引用类型,而不适用于值类型。

不管JIT优化如何,直接指针访问内存都不会很快...如果您在内存中有连续数据,则通过C ++指针(即C指针...让Caesar承担应有的访问权限)访问它的速度会快上很多倍。比Java / C#中的要好。

人们已经观察到Java在SciMark2基准测试的SOR测试中击败了C ++,正是因为指针阻碍了与别名相关的优化。

同样值得注意的是,.NET在链接后确实在动态链接的库之间进行泛型的类型特殊化,而C ++则不能,因为必须在链接之前解析模板。显然,泛型相对于模板的最大优势是可理解的错误消息。


0

除了其他人所说的以外,据我了解,.NET和Java在内存分配方面更胜一筹。例如,它们可以压缩内存,因为内存会变得碎片化,而C ++则不能(本机,但如果您使用的是聪明的垃圾收集器,则可以)。


或者,如果您使用的是更好的C ++分配器和/或对象池。从C ++的角度来看,这远非魔术,它可以归结为使“堆分配”变得与堆栈分配一样快。
paercebal

如果您总是在堆上分配所有内容,那么.NET和Java甚至可能比C / C ++表现更好。但是您只是不会在C / C ++中执行此操作。
弗伦西(Frunsi)

0

对于需要大量速度的任何事物,JVM只是调用C ++实现,因此,与大多数操作系统相关的事情相比,JVM的性能要好得多是一个问题。垃圾回收会将您的内存减少一半,但是使用一些更高级的STL和Boost功能将具有相同的效果,但潜在的错误几倍。

如果您仅在具有许多类的大型项目中使用C ++库及其许多高级功能,则可能会比使用JVM慢。除了更容易出错。

但是,C ++的好处是它允许您优化自己,否则您将被编译器/ jvm所困扰。如果您创建自己的容器,编写自己的对齐内存管理,使用SIMD,然后在此处进行组装,则可以比大多数C ++编译器自行完成的速度提高至少2到4倍。对于某些操作,为16x-32x。那就是使用相同的算法,如果您使用更好的算法并进行并行化,则增长可能会非常惊人,有时会比常用方法快数千倍。


0

我从几个不同的角度来看它。

  1. 给定无限的时间和资源,托管或非托管代码会更快吗?显然,答案是非托管代码在这方面始终至少可以绑定托管代码-在最坏的情况下,您只是对托管代码解决方案进行硬编码。
  2. 如果您使用一种语言来学习程序,然后直接将其翻译为另一种语言,那么它的性能会降低多少?对于任何两种语言,可能很多。大多数语言需要不同的优化并具有不同的陷阱。微观性能通常与了解这些细节有关。
  3. 在有限的时间和资源下,两种语言中哪一种会产生更好的结果?这是最有趣的问题,因为尽管托管语言生成的代码可能会稍微慢一些(考虑到合理地为该语言编写的程序),但该版本可能会尽快完成,从而将更多的时间花费在优化上。

0

一个简短的答案:在固定预算的情况下,您将获得比C ++应用程序更好的Java应用程序性能(考虑到ROI)。此外,Java平台具有更完善的分析器,可帮助您更快地确定热点

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.