我在许多问题中都注意到了SE上一个经常发生的主题,那就是持续存在的论点,即C ++比Java之类的高级语言更快和/或更有效。相反的论点是,由于JIT等原因,现代JVM或CLR可以同样高效,因为它可以处理越来越多的任务,而C ++仅在知道自己在做什么以及为什么以某种方式做事的情况下才更有效率将会提高性能。这很明显并且很有意义。

我想知道一个基本的解释(如果有这样的事情……),为什么在C ++中某些任务比JVM更快,为什么更快? CLR?仅仅是因为C ++被编译为机器代码,而JVM或CLR在运行时仍然具有JIT编译的处理开销吗?

当我尝试研究该主题时,发现的所有内容都是相同的上面我只是概述了这些信息,而没有任何详细的信息来确切地了解如何将C ++用于高性能计算。

评论

性能还取决于程序的复杂性。

我要补充一点:“只有知道您在做什么,以及为什么以某种方式做事才能提高性能,C ++才能变得更加高效。”通过说,这不仅是知识问题,而且是开发人员时间问题。最大化优化并不总是有效的。这就是为什么存在诸如Java和Python之类的高级语言的原因(还有其他原因)-减少程序员为完成给定任务而花费编程的时间,却以高度优化的优化为代价。
@Joel Cornett:我完全同意。我在Java中肯定比在C ++中生产力更高,并且仅在需要编写非常快速的代码时才考虑使用C ++。另一方面,我看到编写得不好的C ++代码真的很慢:C ++在不熟练的程序员手中没那么有用。

JIT可以生成的任何编译输出都可以由C ++生成,但是C ++可以生成的代码不一定由JIT生成。因此,C ++的功能和性能特性是任何高级语言的功能和性能的超集。 Q.E.D

@Doval从技术上讲是正确的,但通常,您可以一方面计算可能会影响程序性能的运行时因素。通常无需使用两个以上的手指。因此,最坏的情况是您会发送多个二进制文件……只是事实证明您甚至不需要这样做,因为潜在的加速可以忽略不计,这就是为什么没人打扰的原因。

#1 楼

都是关于内存(而不是JIT)的。 JIT的“ C优势”主要限于通过内联优化虚拟或非虚拟调用,而CPU BTB已经在努力做到这一点。

在现代机器中,访问RAM确实很慢(与CPU所做的任何事情相比),这意味着可以尽可能多地使用高速缓存的应用程序(当使用较少的内存时更容易)比没有的速度快一百倍Java使用多种方法来比C ++使用更多的内存,并使编写利用缓存充分利用的应用程序变得更加困难:


每个内存开销至少为8个字节。对象,并且在很多地方(即标准集合)都要求或首选使用对象而不是基元。
字符串由两个对象组成,开销为38个字节。

内部使用UTF-16,这意味着每个ASCII字符需要两个字节而不是一个字节(Oracle JVM最近引入了一个为避免纯ASCII字符串而避免这种情况的优化。)
没有聚合引用类型(即结构),因此也没有聚合引用类型的数组。与C结构和数组相比,Java对象或Java对象数组的L1 / L2缓存局部性很差。
Java泛型使用类型擦除,与类型实例化相比,它具有较差的缓存局部性。
对象分配是不透明的,必须针对每个对象分别进行,因此应用程序不可能以缓存友好的方式故意布置其数据并将其仍视为结构化数据。

其他一些与内存相关但与缓存无关的因素:


没有堆栈分配,因此必须使用所有非原始数据堆并进行垃圾收集(某些情况下,最近的一些JIT在后台进行堆栈分配)。
因为没有聚集引用类型,所以没有堆栈传递聚集引用类型。 (认为​​Vector参数的有效传递)
垃圾回收会损害L1 / L2高速缓存的内容,而GC暂停世界暂停会损害交互性。
在数据类型之间进行转换始终需要复制;您不能将指针指向从套接字获得的一堆字节并将其解释为浮点数。

其中一些是折衷方案(不必进行手动内存管理值得付出很多努力对于大多数人而言,这可能是尝试保持Java简单性的结果,还有一些是设计错误(尽管可能只是事后才知道,即UTF-16在创建Java时是固定长度的编码,因此可以决定选择它更容易理解)。

值得注意的是,对于Java / JVM,许多折衷方案与对于C#/ CIL的折衷方案有很大不同。 .NET CIL具有引用类型的结构,堆栈分配/传递,结构的打包数组以及类型实例化的泛型。

评论


+1-总的来说,这是一个很好的答案。但是,我不确定“没有堆栈分配”的要点是否完全正确。 Java JIT通常会进行转义分析,以便在可能的情况下分配堆栈-也许您应该说Java语言不允许程序员确定对象何时是堆栈分配的还是堆分配的。另外,如果正在使用世代垃圾收集器(所有现代JVM都使用该垃圾收集器),则“堆分配”意味着与C ++环境中完全不同的东西(具有完全不同的性能特征)。

–丹尼尔·普赖登(Daniel Pryden)
2012年8月3日19:20

我认为还有另外两件事,但是我主要是在更高层次上工作,所以请告诉我我是否错了。如果不对内存中实际发生的事情以及机器代码的实际工作方式有更全面的了解,那么您就无法真正编写C ++,而脚本或虚拟机语言会将所有这些东西从您的视线中抽离出来。您还可以更精细地控制事物的工作方式,而在VM或解释语言中,您则依赖于核心库作者可能针对过分特定的场景进行了优化的内容。

–埃里克·雷彭(Erik Reppen)
2012年8月3日在21:10



+1。我还要添加一件事(但不愿意提出新的答案):Java中的数组索引总是涉及边界检查。对于C和C ++,情况并非如此。

–riwalk
2012年8月3日在21:28

值得注意的是,Java的堆分配比使用C ++的朴素版本要快得多(由于内部池化和其他功能),但是如果您知道自己在做什么,则C ++中的内存分配可以明显更好。

–布伦丹·朗(Brendan Long)
2012年8月3日在21:52

@BrendanLong,是的..但只有在内存干净的情况下-一旦应用运行了一段时间,由于需要GC,内存分配将变慢,因为它需要释放内存,运行终结器然后大幅降低运行速度紧凑。这是一个折衷方案,可以使基准测试受益,但(IMHO)总体上会使应用程序变慢。

– gbjbaanb
2012年8月3日在22:43

#2 楼


是否仅仅是因为C ++被编译为汇编代码/机器代码
,而Java / C#在运行时仍具有JIT编译的处理开销?


部分但总的来说,假设有绝对出色的最新JIT编译器,由于两个主要原因,正确的C ++代码仍会比Java代码表现更好:

1)C ++模板提供了更好的工具来编写既通用又高效的代码。模板为C ++程序员提供了非常有用的抽象,具有零运行时开销。 (模板基本上是编译时的鸭子式输入。)相反,Java泛型所带来的最好的效果基本上是虚函数。虚函数始终具有运行时开销,通常不能内联。

一般来说,大多数语言,包括Java,C#甚至C语言,都可以让您在效率和通用性/抽象性之间进行选择。 C ++模板为您提供了这两种方法(以更长的编译时间为代价。)

2)C ++标准对已编译的C ++程序的二进制布局没有太多说的事实为C ++编译器与Java编译器相比,它有更多的回旋余地,从而可以实现更好的优化(有时会付出更多调试困难的代价。)实际上,Java语言规范的本质在某些方面会导致性能下降。例如,Java中不能有连续的Objects数组。您只能有一个连续的对象指针(引用)数组,这意味着在Java中对数组进行迭代总是会导致间接开销。但是,C ++的值语义支持连续数组。另一个区别是C ++允许在堆栈上分配对象,而Java不允许,这意味着实际上,由于大多数C ++程序倾向于在堆栈上分配对象,因此分配成本通常接近于零。 br />
在任何需要在堆上分配许多小对象的情况下,C ++都可能落后于Java。在这种情况下,Java的垃圾收集系统可能会比C ++中的标准newdelete产生更好的性能,因为Java GC支持批量释放。但是同样,C ++程序员可以通过使用内存池或slab分配器来弥补这一点,而Java程序员在面对Java运行时未针对其优化的内存分配模式时却无权求助。

此外,有关此主题的更多信息,请参见此出色的答案。

评论


好的答案,但有一点要指出:“ C ++模板可以为您提供这两种功能(以更长的编译时间为代价。)”我还要以更大的程序尺寸为代价来添加它们。可能并不总是一个问题,但是如果针对移动设备进行开发,肯定会成为问题。

–狮子座
2012年8月3日在23:54



@luiscubal:不,就此而言,C#泛型非常类似于Java(因为无论传递哪种类型,都采用相同的“泛型”代码路径。)C ++模板的窍门是,该代码为适用于每种类型。因此std :: vector 是一个仅针对int设计的动态数组,并且编译器能够相应地对其进行优化。 C#List 仍然只是一个列表。

–杰夫
2012年8月4日在9:52

@jalf C#List 使用int [],而不是Java使用的Object []。参见stackoverflow.com/questions/116988/…

–luiscubal
2012年8月4日在12:52

@luiscubal:您的术语不清楚。 JIT并没有按照我认为的“编译时”执行。当然,您是对的,因为有了一个足够聪明和积极的JIT编译器,它可以做什么实际上没有任何限制。但是C ++要求这种行为。此外,C ++模板允许程序员指定显式专业化,并在适用的情况下启用其他显式优化。 C#没有与此等效的功能。例如,在C ++中,我可以定义一个vector ,其中对于vector <4>的特定情况,应使用我的手工编码SIMD实现

–杰夫
2012年8月4日15:17



@Leo:15年前,通过模板进行代码膨胀是一个问题。自从有了大量的模板化和内联功能之后,再加上编译器的能力(例如折叠相同的实例),如今通过模板,很多代码变得更小。

–sbi
2012年8月7日在9:09

#3 楼

其他答案(到目前为止有6个)似乎忘记了,但是我认为对于回答这个问题非常重要的是C ++的非常基本的设计哲学之一,它是由Stroustrup从第一天开始制定和采用的:

您不用为不使用的东西付钱。

还有其他一些重要的底层设计原则极大地影响了C ++(例如,不应强迫您使用C ++)特定范式),但您不用为最重要的东西付钱。


Stroustrup在他的《 C ++的设计和演进》(通常称为[D&E])一书中,描述了他最初提出C ++的需求。用我自己的话说:对于他的博士学位论文(与IIRC有关的网络仿真有关),他在SIMULA中实现了一个他非常喜欢的系统,因为该语言非常擅长允许他直接在代码中表达自己的想法。但是,生成的程序运行得太慢了,为了获得学位,他用C的前身BCPL重写了这个东西。用BCPL编写代码,他认为这很痛苦,但是生成的程序足够快,可以交付结果,使他能够完成博士学位。

之后,他想要一种语言,该语言可以将现实世界中的问题尽可能直接地转换为代码,同时又可以使代码非常高效。
他创造了后来成为C ++的东西。


因此,以上引用的目标不仅是几个基本的基础设计原则之一,而且非常接近C ++的存在理由。而且可以在该语言的几乎所有位置找到它:当您需要函数时,函数仅为virtual(因为调用虚拟函数会带来一些开销),只有在您明确请求时才自动初始化POD,而在您使用时,例外只会降低性能实际上是将它们扔掉了(尽管这是一个明确的设计目标,使堆栈框架的设置/清理非常便宜),但任何时候都不会运行GC,等等。

C ++明确选择不这样做给您带来一些便利(“我必须在这里将此方法虚拟吗?”)以换取性能(“不,我不,现在编译器可以对其进行优化,并从整体上优化了!”) ,并且毫不奇怪,与更方便的语言相比,这确实带来了性能提升。

评论


您不用为不使用的东西付费。 =>然后添加了RTTI :(

– Matthieu M.
2012年8月5日在9:49

@Matthieu:虽然我理解您的看法,但我不禁注意到,即使在性能方面也有所增加。指定RTTI以便可以使用虚拟表来实现它,因此如果不使用它,则只会增加很少的开销。如果您不使用多态,那么根本就没有成本。我想念什么吗?

–sbi
2012年8月5日13:11

@Matthieu:当然是有原因的。但是这个原因合理吗?从我可以看到,“ RTTI成本”(如果未使用)是每个多态类的虚拟表中的一个附加指针,指向静态分配在某个地方的某些RTTI对象。除非您要在我的烤面包机中对芯片进行编程,否则这将是如何相关的?

–sbi
2012年8月6日7:15



@Aaronaught:我对此无所适从。您真的否定我的答案,是因为它指出了使Stroustrup等人以允许性能的方式添加功能的基本原理,而不是单独列出这些方式和功能?

–sbi
2012年8月7日在9:11

@Aaronaught:你有我的同情。

–sbi
2012年8月8日在9:30

#4 楼

您知道有关该主题的Google研究论文吗?

从结论中得出的结论:


我们发现,就性能而言,C ++大获成功< br保证金。但是,它还需要最广泛的调整工作,
其中许多工作都是在复杂的水平上完成的,而对于普通程序员而言则是



这至少是部分解释,因为“由于经验方法,现实世界中的C ++编译器比Java编译器生成更快的代码”。

评论


除了内存和缓存使用率差异之外,最重要的一项是执行的优化量。比较一下GCC / LLVM(可能还有Visual C ++ / ICC)相对于Java HotSpot编译器所做的优化:还有更多的优化,尤其是关于循环,消除冗余分支和寄存器分配的优化。 JIT编译器通常没有时间进行这些积极的优化,甚至认为他们可以使用可用的运行时信息更好地实现它们。

– Gratian Lup
2012年8月9日在8:03



@ GratianLup:我想知道LTO是否(还是)正确。

–重复数据删除器
2015年2月22日19:36



@GratianLup:让我们不要忘记针对C ++的配置文件引导优化...

–重复数据删除器
16年1月27日在16:15

#5 楼

这不是您所提问题的重复,但是被接受的答案回答了您大部分的问题:
Java的现代回顾

总结:


从根本上讲,Java的语义规定它是一种比C ++慢的语言。


因此,取决于与C ++进行比较的其他语言,您可能会得到或答案是否相同。

在C ++中,您具有:


执行智能内联的能力,具有强局部性的通用代码生成(模板) )
尽可能小而紧凑的数据
避免间接访问的机会
可预测的内存行为
仅由于使用高级抽象(模板)而可能进行编译器优化

这些是语言定义的功能或副作用,因此从理论上讲,它在内存和速度上的效率要高于任何以下语言:


大规模使用间接(托管参考/指针”语言) :间接意味着CPU必须跳转到内存中才能获取必要的数据,从而增加CPU缓存故障,这意味着减慢了处理速度-即使C可以包含少量数据,C也会大量使用间接寻址;
生成会间接访问成员的大型对象:这是默认情况下具有引用的结果,成员是指针,因此当您获得成员时,您可能无法获得接近父对象核心的数据,从而再次触发高速缓存未命中。 />使用垃圾收集器:这只会使性能的可预测性无法实现(通过设计)。

编译器的C ++积极内联减少或消除了许多间接调用。如果您不将这些数据散布在整个内存中而不是打包在一起,则生成少量压缩数据的能力使其易于缓存(二者皆有可能,C ++任您选择)。 RAII使C ++内存行为可预测,从而消除了需要高速进行的实时或半实时仿真的情况下的许多问题。通常,可以通过以下方式总结局部性问题:程序/数据越小,执行速度越快。 C ++提供了多种方法来确保您的数据位于所需位置(在池,数组或其他任何位置)并且结构紧凑。

显然,还有其他语言可以做到相同,但是它们不那么受欢迎,因为它们没有提供像C ++一样多的抽象工具,因此在许多情况下它们的用处不大。

#6 楼

它主要是关于内存(正如Michael Borgwardt所说的),其中添加了一些JIT低效率。连续地(即全部在一起)。现在,使用GC系统,可以在GC堆上分配内存,这很快,但是随着内存的使用,GC将定期启动并删除不再使用的块,然后将其余块压缩在一起。现在,除了将这些用过的块移动到一起的明显缓慢之外,这意味着您正在使用的数据可能不会粘在一起。如果您有一个由1000个元素组成的数组,除非您一次分配所有元素(然后更新它们的内容,而不是删除并创建新的元素-将在堆的末尾创建),否则这些元素将散布在整个堆中,因此需要多个内存命中才能将它们全部读取到CPU缓存中。 C / C ++应用程序很可能会为这些元素分配内存,然后使用数据更新模块。 (好吧,像列表这样的数据结构的行为更像是GC内存分配,但是人们知道它们比矢量要慢)。

您可以通过将任何StringBuilder对象替换为来看到这一点字符串... Stringbuilders通过预分配内存并填充来工作,这是java / .NET系统的已知性能技巧。

别忘了Java / C#中“删除旧副本并分配新副本”的范例非常多,仅因为人们被告知由于GC内存分配确实非常快,所以分散的内存模型无处不在(当然,除了stringbuilders之外),因此所有库都倾向于浪费内存并大量使用内存,而这些都无法获得连续性的好处。怪罪GC的炒作了-他们告诉你记忆是免费的,哈哈。

GC本身显然是另一个性能优势-运行时,它不仅必须扫过堆,而且还必须释放所有未使用的块,然后必须运行任何终结器(尽管以前必须单独完成下次应用程序停止运行时(我不知道它是否仍然受到如此大的打击,但我读过的所有文档都说,如果确实需要,仅使用终结器),然后必须将这些块移到适当的位置,以便堆压缩,并将引用更新为该块的新位置。如您所见,它的工作量很大!

C ++内存的性能优势取决于内存分配-当您需要一个新块时,您必须遍历堆以寻找下一个可用空间,足够大,堆非常分散,这几乎不像GC的“仅在末尾分配另一个块”那样快,但是我认为它不如GC压缩的所有工作那么慢,并且可以通过以下方法来缓解使用多个固定大小的块堆(也称为内存池)。

还有更多...类似于将程序集从GAC中加载,这需要进行安全检查,探测路径(打开sxstrace并查看和即将进行的其他过度工程似乎在Java / .net中比C / C ++流行得多。

评论


您编写的许多内容对于现代的垃圾收集器而言并非如此。

–迈克尔·伯格沃德(Michael Borgwardt)
2012年8月7日在8:42

@MichaelBorgwardt如?我说“ GC定期运行”和“它压缩堆”。我剩下的答案都与应用程序数据结构如何使用内存有关。

– gbjbaanb
2014-2-19在11:46

#7 楼

“仅仅是因为C ++被编译为汇编/机器代码,而Java / C#在运行时仍具有JIT编译的处理开销吗?”基本上,是的!

不过,请注意,Java的开销要比JIT编译更多。例如,它为您执行更多检查(这是ArrayIndexOutOfBoundsExceptionsNullPointerExceptions之类的操作)。垃圾收集器是另一个重要的开销。

这里有一个非常详细的比较。

#8 楼

请记住,以下内容仅是比较本机和JIT编译之间的区别,并不涵盖任何特定语言或框架的细节。可能有合理的理由选择除此以外的特定平台。

当我们声称本机代码更快时,我们在谈论本机编译代码与JIT编译代码的典型用例,其中用户将使用JIT编译的应用程序运行,并立即得到结果(例如,无需先等待编译器)。在那种情况下,我认为没有人可以直言不讳地宣称JIT编译后的代码可以匹配或击败本机代码。

假设我们有一个用X语言编写的程序,可以使用本机编译器以及JIT编译器对其进行编译。每个工作流程都有相同的阶段,可以概括为(代码->中间表示->机器代码->执行)。两者之间的最大区别是用户可以看到哪个阶段,程序员可以看到哪个阶段。使用本机编译,程序员可以看到执行阶段以外的所有内容,但是使用JIT解决方案,除了执行之外,用户还可以看到对机器代码的编译。

声称A的速度比B是指用户看到程序运行所花费的时间。如果我们假设这两段代码在执行阶段的性能相同,那么我们必须假设JIT工作流程对用户而言较慢,因为他还必须看到编译为机器代码的时间T,其中T> 0。 ,对于JIT工作流程执行与本地工作流程相同的任何可能性,对于用户,我们必须减少代码的执行时间,以使执行+编译到机器代码的时间低于仅执行阶段本地工作流程。这意味着我们必须在JIT编译中比在本地编译中更好地优化代码。

但是,这是不可行的,因为要执行必要的优化以加快执行速度,我们必须花更多的时间在编译到机器代码的阶段,因此,由于优化的代码而导致的任何保存时间实际上都会丢失,因为我们将其添加到编辑中。换句话说,基于JIT的解决方案的“缓慢”不仅是因为JIT编译需要花费更多的时间,而且编译所产生的代码比本地解决方案要慢。

我会举个例子:寄存器分配。由于内存访问比寄存器访问慢数千倍,因此理想情况下,我们希望尽可能使用寄存器,并尽可能减少内存访问,但是寄存器数量有限,并且在需要时必须将状态溢出到内存中一个寄存器。如果我们使用的寄存器分配算法需要200毫秒的计算时间,结果节省了2毫秒的执行时间-我们没有充分利用JIT编译器的时间。像Chaitin的算法这样的解决方案无法生成高度优化的代码。

JIT编译器的作用是在编译时间和所生成代码的质量之间取得最佳平衡,但是,这样做有很大的偏见。快速编译时间,因为您不想让用户等待。在JIT情况下,正在执行的代码的性能会较慢,因为本机编译器在优化代码方面不受时间的束缚,因此可以自由使用最佳算法。 JIT编译器的整体编译+执行仅可以击败本机编译代码的执行时间的可能性实际上为0。

但是我们的VM不仅限于JIT编译。他们采用提前编译技术,缓存,热插拔和自适应优化。因此,让我们修改一下性能是用户所看到的说法,并将其限制为执行程序所花费的时间(假设我们已经编译了AOT)。我们可以有效地使执行代码等效于本机编译器(或可能更好)。 VM的一个重要主张是,它们可以产生比本机编译器更好的代码质量,因为它可以访问更多信息-正在运行的进程的信息,例如可以执行某个功能的频率。然后,VM可以通过热交换将自适应优化应用于最基本的代码。

这个参数有一个问题-它假定配置文件引导的优化等是VM独有的东西,这就是不对。我们也可以将其应用于本机编译-通过在启用了性能分析的情况下编译应用程序,记录信息,然后使用该概要文件重新编译应用程序。可能还值得指出的是,代码热交换不是JIT编译器可以做到的,我们也可以为本地代码做到这一点-尽管基于JIT的解决方案更容易获得,并且对开发人员来说更容易。因此,最大的问题是:VM可以为我们提供一些本机编译无法提供的信息,从而可以提高代码的性能吗?

我自己看不到。我们也可以将典型VM的大多数技术应用于本机代码-尽管过程更多。同样,我们可以将本机编译器的任何优化应用回使用AOT编译或自适应优化的VM。现实情况是,本机运行的代码与在VM中运行的代码之间的差异并不像我们已经相信的那么大。它们最终会导致相同的结果,但是他们采用了不同的方法来达到目标​​。 VM使用迭代方法来生成优化的代码,本机编译器从一开始就希望它(可以通过迭代方法进行改进)。

C ++程序员可能会说他需要从一劳永逸,并且不应该等待虚拟机弄清楚如何做(如果有的话)。不过,这可能是我们当前技术的一个正确点,因为我们虚拟机中的当前优化水平不如本机编译器可以提供的优化水平,但是,如果我们的虚拟机中的AOT解决方案得到改善等,可能并非总是如此。

#9 楼

本文是一组博客文章的摘要,这些文章试图比较c ++和c#的速度,以及为了获得高性能代码而必须用两种语言解决的问题。总结是:“您的库比任何事情都重要,但是如果您使用的是c ++,则可以克服这一点。”或“现代语言具有更好的库,因此可以用较少的精力获得更快的结果”,具体取决于您的哲学偏见。

#10 楼

我认为这里的真正问题不是“哪个更快?”但是“哪个具有更高性能的最佳潜力?”。从这些术语来看,C ++显然胜出了-它被编译为本机代码,没有JITting,它的抽象水平较低,等等。

故事还很遥远。 />因为C ++已编译,所以任何编译器优化都必须在编译时进行,而适用于一台计算机的编译器优化可能完全不适用于另一台计算机。在这种情况下,任何全局编译器优化都可以并且会偏向某些算法或代码模式,而不是其他算法。预编译的程序不能并且可以针对其实际运行的机器以及其实际运行的代码进行非常具体的优化。一旦超过了JIT的初始开销,在某些情况下它就有可能变得更快。

在两种情况下,算法的合理实现以及程序员不愚蠢的其他实例都可能远远不够。然而,更重要的因素-例如,完全有可能用C ++编写完全死脑筋的字符串代码,即使是解释性脚本语言也可能会陷入困境。

评论


“适用于一台计算机的编译器优化可能完全不适用于另一台计算机”。这并不是该语言的真正责任。确实,对于性能至关重要的代码,可以针对将在其上运行的每台计算机分别进行编译,如果您从源代码本地编译(-march = native),这是不费吹灰之力的。 —“这是较低的抽象级别”并不是真的。 C ++使用与Java一样的高级抽象(或者,实际上,是更高层次的抽象:函数式编程或模板元编程),它实现的抽象程度不如Java。

–leftaround关于
2012年8月5日上午11:00

“确实,对于性能至关重要的代码,可以针对将在其上运行的每台计算机分别进行编译,如果您从源代码本地进行编译,这是理所当然的事情”,这失败了,因为最终用户也是程序员。

– Maximus Minimus
2012年8月5日在16:29

不一定是最终用户,只有负责安装程序的人员。在台式机和移动设备上,通常是最终用户,但是这些并不是唯一的应用程序,当然也不是最关键的应用程序。如果您像所有优秀的自由/开放软件项目一样正确编写了构建脚本,那么您实际上并不需要成为一名程序员即可从源代码构建程序。

–leftaround关于
2012年8月5日在16:40



从理论上讲,JIT可以比静态编译器发挥更多的技巧,但实际上(至少对于.NET,我也不懂Java),实际上它并没有做任何事情。最近,我对.NET JIT代码进行了许多分解,并且有各种各样的优化措施,例如,使代码提升循环,消除无效代码等,.NET JIT根本无法做到。我希望这样做,但是嘿,微软内部的Windows团队多年来一直试图杀死.NET,所以我没有屏息

– Orion Edwards
2012年8月5日在21:29

#11 楼

JIT编译实际上会对性能产生负面影响。如果您设计一个“完美”的编译器和一个“完美”的JIT编译器,则第一个选择将始终在性能上胜出。

Java和C#都被解释为中间语言,然后在运行时编译为本机代码,从而降低了性能。

但是对于C#而言,区别并不明显:Microsoft CLR为不同的CPU生成了不同的本机代码,因此使代码对于运行它的计算机更加有效,而C ++编译器并不总是这样做。

P.S. C#编写效率很高,并且没有很多抽象层。对于Java而言,情况并非如此,效率不高。因此,在这种情况下,凭借其出色的CLR,C#程序通常表现出比C ++程序更好的性能。有关.Net和CLR的更多信息,请查看Jeffrey Richter的“通过C#进行CLR”。

评论


如果JIT实际上对性能产生负面影响,那么肯定不会使用它吗?

–救世主
2012年8月3日14:39

@Zavior-我想不出您的问题的好答案,但是我看不到JIT如何无法增加额外的性能开销-JIT是在运行时完成的额外过程,需要的资源并不多。花费在程序本身的执行上,而完全编译的语言“可以使用”。

–匿名
2012年8月3日14:43

如果将JIT放在上下文中,它将对性能产生积极的影响,而不会产生负面影响-在运行它之前,它将JIT编译为机器代码。还可以缓存结果,使其运行速度比解释的等效字节码快。

–Casey Kuball
2012年8月3日14:56

JIT(或更确切地说,字节码方法)不是为了提高性能而是为了方便。无需为每个平台(或一个公共子集,对于每个平台来说都是次优的)预先构建二进制文件,而是仅中途编译,然后让JIT编译器完成其余工作。 “写一次,部署到任何地方”就是这样做的原因。仅使用字节码解释器就可以带来便利,但是JIT确实比原始解释器快(尽管不一定足够快才能击败预编译的解决方案; JIT编译确实需要时间,并且结果并不总是能弥补)为了它)。

– tdammers
2012年8月3日15:06

@Tdammmers,实际上也有一个性能组件。请参阅java.sun.com/products/hotspot/whitepaper.html。优化可以包括诸如动态调整以改善分支预测和缓存命中率,动态内联,去虚拟化,禁用边界检查以及循环展开之类的事情。声称在许多情况下,这些支付的费用远远超过了JIT的费用。

–Charles E. Grant
2012年8月3日在16:54