是什么支持C ++可以比使用JIT的JVM或CLR更快的说法呢?[关闭]


119

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

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

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


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

23
我要补充一点:“只有知道您在做什么,以及为什么以某种方式做事才能提高性能,C ++才能变得更加高效。” 通过说,这不仅是知识问题,而且是开发人员时间问题。最大化优化并不总是有效的。这就是为什么存在诸如Java和Python之类的高级语言(还有其他原因)的原因-减少程序员为了高度完成优化而不得不花费时间来完成给定任务的时间。
乔尔·科内特

4
@乔尔·科内特:我完全同意。我在Java中肯定比在C ++中生产力更高,并且仅在需要编写非常快速的代码时才考虑使用C ++。另一方面,我看到编写得不好的C ++代码真的很慢:C ++在不熟练的程序员手中没那么有用。
乔治

3
JIT可以产生的任何编译输出都可以由C ++产生,但是C ++可以产生的代码不一定由JIT产生。因此,C ++的功能和性能特性是任何高级语言的功能和性能的超集。QED
tylerl 2012年

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

Answers:


200

都是关于内存(而不是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具有引用类型的结构,堆栈分配/传递,结构的打包数组以及类型实例化的泛型。


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

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

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

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

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

67

仅仅是因为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 ++程序都倾向于在堆栈上分配对象,因此分配成本通常接近于零。

在任何需要在堆上分配许多小对象的情况下,C ++都可能落后于Java。在这种情况下,由于Java GC支持批量释放,因此Java的垃圾回收系统可能会比标准newdeleteC ++的性能更好。但是同样,C ++程序员可以通过使用内存池或平板分配器来弥补这一点,而Java程序员在面对Java运行时未针对之优化的内存分配模式时却无权求助。

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


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

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

12
@jalf C#List<int>使用int[],而不是Object[]Java。见stackoverflow.com/questions/116988/...
luiscubal

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

5
@Leo:通过模板进行代码膨胀是15年前的问题。自从有了大量的模板化和内联,再加上能力强的编译器(如折叠相同的实例)以来,如今许多代码通过模板变得越来越
2012年

46

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

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

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


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

之后,他希望使用一种语言,该语言可以将现实世界中的问题尽可能直接地转换为代码,同时还可以使代码非常高效。
为此,他创建了后来成为C ++的软件。


因此,上面引用的目标不仅是几个基本的基本设计原则之一,而且非常接近C ++ 的存在理由。可以在该语言的几乎任何地方找到它:函数仅virtual在您想要它们时使用(因为调用虚拟函数会带来少量开销)POD仅在您明确请求时才自动初始化,而异常只会在您实际需要时降低性能扔掉它们(这是一个明确的设计目标,即允许非常廉价地设置/清理堆栈帧),在需要时不运行GC等。

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


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

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

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

4
@Aaronaught:我不知该如何回答。您真的否定我的回答,是因为它指出了使Stroustrup等人以允许性能的方式添加功能的基本原理,而不是单独列出这些方式和功能?
2012年

9
@Aaronaught:你有我的同情。
2012年

29

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

从结论:

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

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


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

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

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

23

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

总结一下:

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

因此,取决于您使用哪种其他语言比较C ++,您可能会得到或不同的答案。

在C ++中,您具有:

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

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

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

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

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


7

它主要是关于内存的(如Michael Borgwardt所说),并添加了一些JIT效率低下的问题。

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

您可以通过用String替换任何StringBuilder对象的方式在操作中看到这一点。Stringbuilders通过预分配内存并填充它来工作,这是java / .NET系统的已知性能技巧。

不要忘记在Java / C#中使用了“删除旧副本并分配新副本”的范例,这仅仅是因为人们被告知由于GC导致内存分配的速度非常快,因此分散的内存模型得到了无处不在的使用(当然,除了stringbuilder之外),因此您的所有库都倾向于浪费内存并使用很多内存,而这些都没有从连续性中受益。怪罪GC的炒作了-他们告诉你记忆是免费的,哈哈。

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

对C ++内存的性能影响取决于内存分配-当您需要一个新块时,您必须遍历堆以寻找下一个足够大的可用空间,且该堆具有高度分散的内存,这几乎不及GC的快“仅在最后分配另一个块”,但我认为它不如GC压缩的所有工作慢,并且可以通过使用多个固定大小的块堆(也称为内存池)来缓解。

还有更多...像从GAC中加载程序集需要安全检查,探查路径(打开sxstrace并查看它要达到的目的!)以及一般其他过度工程似乎在java / .net上更为流行。比C / C ++


2
您写的很多东西对于现代的垃圾收集器来说都不是正确的。
Michael Borgwardt

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

6

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

不过,请快速注意一下,Java的开销不仅仅是JIT编译。例如,它会为您做更多的检查(这是它像ArrayIndexOutOfBoundsExceptionsand 一样的工作方式NullPointerExceptions)。垃圾收集器是另一个重要的开销。

有一个非常详细的对比在这里


2

请记住,以下内容仅是比较本机编译和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 ++程序员可能会争辩说,他需要从一开始就进行优化,因此根本不应该等待VM确定如何进行优化。但是,这可能是我们当前技术的一个正确点,因为我们虚拟机中的当前优化水平不如本机编译器可以提供的优化水平,但是,如果我们的虚拟机中的AOT解决方案得到改进,则并非总是如此。


0

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


0

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

这还远没有完整。

因为C ++是编译的,所以任何编译器优化都必须在编译时完成,而适用于一台计算机的编译器优化可能完全不适用于另一台计算机。在这种情况下,任何全局编译器优化都可以并且会比其他算法更喜欢某些算法或代码模式。

另一方面,JITted程序将在JIT时进行优化,因此它可以提取一些预编译程序无法做到的窍门,并且可以对其实际运行的计算机和实际运行的代码进行非常具体的优化。一旦超过了JIT的初始开销,在某些情况下它就有可能变得更快。

在这两种情况下,合理的算法实现和程序员其他不愚蠢的情况都可能是更重要的因素,但是,例如,完全有可能用C ++编写完全死脑筋的字符串代码,这会被甚至一种解释性脚本语言。


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

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

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

1
从理论上讲,JIT可以比静态编译器发挥更多的技巧,但在实践中(至少对于.NET,我也不了解Java),实际上它并没有做任何事情。最近,我已经对.NET JIT代码进行了很多分解,并且有各种各样的优化措施,例如,使代码脱离循环,消除无效代码等,.NET JIT根本无法做到。我希望它会,但是,嘿,Windows团队里微软一直试图杀死.NET多年,所以我不会屏住了呼吸
猎户座爱德华兹

-1

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

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

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

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


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

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

3
JIT有一个积极的对性能的影响,而不是消极的,如果你把它的来龙去脉-这是编译的字节代码转换成机器代码运行它之前。还可以缓存结果,使其运行速度比解释的等效字节码快。
Casey Kuball 2012年

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

4
@Tdammmers,实际上也有一个性能组件。请参阅java.sun.com/products/hotspot/whitepaper.html。优化可以包括诸如动态调整以改善分支预测和缓存命中率,动态内联,去虚拟化,禁用边界检查以及循环展开之类的事情。这种说法是,在许多情况下,这些服务可以支付JIT的费用。
查尔斯·格兰特
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.