为什么用JIT编译器很难在应用性能方面击败AOT编译器?

44

我认为JIT编译器最终会在编译代码的性能方面击败AOT编译器,因为JIT具有固有优势(可以使用仅在运行时可用的信息)。 一个论点是AOT编译器可以花费更多时间编译代码,但服务器VM也可能花费很多时间。

我确实理解JIT似乎在某些情况下确实打败了AOT编译器,但在大多数情况下它们仍然落后于AOT编译器。

那么我的问题是,是什么具体而棘手的问题阻止了JIT编译器击败AOT编译器呢?

编辑:
一些常见的论点:

  • AOT编译器可以花更多时间进行高级优化 -> 如果您正在运行服务器VM几天,您可以花费同样长的时间甚至更长时间。
  • 字节码解释的成本 -> 大多数JIT编译器这些天都缓存本机机器指令。

再次编辑:
有关具体示例,请参见此文章:Improving Swing Performance: JIT vs AOT Compilation。从这篇文章中我可以了解到,作者基本上是在说当没有热点时,拥有运行时信息的优势会降低,因此没有JIT开销的AOT获胜。 但是40%吗? 这似乎没有太多意义。 是所比较的JIT编译器没有针对这种情况进行调整,还是有更根本的原因呢?


3
你似乎完全没有理解“只是”的概念。编译器的性能是由人类而不是机器来衡量的。没有人会容忍等待10秒钟。 - Hans Passant
1
@Hans Passant:在客户端虚拟机中,我明白这一点。但在服务器虚拟机中呢?例如,JVM就有这种区别。 - Enno Shioji
我想这归结为“为什么编写一个发出良好优化的机器代码的编译器很难?”而将Java / JIT与C ++ / AOT进行比较就像比较苹果和橙子一样。(除非谈论到特定的 Java AOT / JIT?)虽然AOT“有更多时间”来完成整个过程,但不要忘记javac已经完成了大量编译 - 词法分析,解析,一些优化,尽管是针对“虚拟机” - 本身。 - user166390
@Rob:是啊,但这样会导致40%的性能差异吗?至少,当JIT检测到热点优化不值得时,它可以开始像AOT一样运行。这似乎是一个相当容易解决的问题。 - Enno Shioji
@EnnoShioji:刚看到你接受了;我已经忘记了这个问题。有几件事情;JIT编译器不能在运行时“切换”到AOT,因为AOT的定义是在执行时间之前发生的。不幸的是,大量的实现细节最终构成了这40%的差异,因此很难确定。 - Rob
显示剩余2条评论
2个回答

35

在JIT和AOT(ahead-of-time)编译之间存在明显的权衡关系。

正如您所说,JIT可以访问有助于优化的运行时信息。这包括关于执行机器的数据,从而实现平台特定的本地化优化。然而,JIT也需要将字节码转换为本地指令的开销。

这种开销通常在需要快速启动或近乎实时响应的应用程序中变得明显。如果机器没有足够的资源进行高级优化,或者代码的性质使其无法进行“激进优化”,那么JIT也不是很有效。

例如,引用自您提供的文章

......在没有明确的性能瓶颈的情况下,我们应该改善什么?正如您可能已经猜到的那样,对于基于配置文件的JIT编译器,存在相同的问题。与其激进地优化少数热点,倒不如保留大量“温暖点”。

AOT编译器也可以像它们想要的那样花费时间进行优化,而JIT编译受时间要求(以保持响应能力)和客户机器的资源限制。因此,AOT编译器可以执行过于昂贵的复杂优化操作,而这些操作在JIT期间则会变得无法承受。

还请参见此SO问题:JIT编译器与离线编译器。


9
愿意花更多时间构建代码,代码就能运行得更快。如果AOT编译器的使用者愿意给构建过程更多的时间,它将能够产生更有效率的代码。然而,JIT编译的一个优势是对于永远不运行的代码来说,其速度相比于AOT编译是无限快的。在一些涉及.NET泛型的情况下,有限(甚至很少)量的源代码可以指定一个无界的方法族,每个方法都需要单独的机器码;如果只运行了十二个这样的方法,那么只有这十二个方法将被编译成机器码。 - supercat
关于基于配置文件的AOT编译,您有什么想法? - Jessie Lesbian

1

编译和优化是代码、上下文和时间的函数。

原始权衡

AOT 可以使用尽可能长的时间进行深度代码分析,了解执行平台并提供额外的优化提示。缺点是构建速度较慢,代码可移植性较差,基于错误假设的糟糕优化以及在运行时缺乏对反射等高级功能的支持。

JIT 更具可移植性,并且可以在运行时支持高级代码更改,但启动时间较长,时间和资源有限,这意味着优化有限。

现代发展

最新的JIT编译器速度更快,启动速度更快,同时仍然产生良好的代码,缩小了与AOT启动延迟之间的差距。

多个编译通道允许JIT在程序运行时不断优化,导致在热身期后与AOT类似的性能。 JIT可以查看实际使用的代码,它被使用的频率以及其他因素(如运行硬件),这些在AOT编译期间不可用。

最先进的JIT编译器可以满足或超过AOT性能,并在更多场景下保持该性能。 AOT仍然是快速启动和一致/可预测操作的最佳选择,但不再是最佳整体性能的默认选项。


2
AOT 可以像 JIT 一样具有可移植性(在某些情况下,AOT 和 JIT 使用完全相同的字节码 - 例如 Microsoft 的 CIL);而 JIT 可能“根本不可移植”(例如,您想要使用的系统没有 JIT 编译器)。JIT 还可能缺少或存在错误的优化(几乎总是有故意忽略的优化,因为它们在运行时太昂贵了)。 - Brendan
为了性能考虑,AOT 通常比它本应该的慢(例如,由开发人员针对通用目标进行优化,而不是针对用户特定的机器进行优化),但这是当前工具的弱点(而不是 AOT 本身的弱点);现代 JIT 仍然无法像“不太好的 AOT”一样好。为了弥补“高级 JIT”性能差的问题,它们几乎总是依赖于大量包含“AOT 编译”的本地代码的库来隐藏性能灾难。从未有过任何“高级 JIT”能够在公平比较中击败 AOT(也永远不会)。 - Brendan
@Brendan 那些是吹毛求疵的假设,而不是关于概念的争论。CIL 是无关紧要的。JIT 更具可移植性,因为它可以在许多机器上运行相同的构件,而不需要匹配的预构建二进制文件。没有 JIT 编译器可用与平台没有 AOT 支持是一样的(一个实现细节)。正如所述,次要传递通过在应用程序生命周期内分摊编译并匹配实际使用情况来克服任何运行时限制。JIT 可以做到 AOT 能做的所有事情,但还可以通过使用 AOT 基本上无法使用的输入因素来更好地完成任务。 - Mani Gandham
请帮我做几件事——在尝试告诉我AOT无法为特定机器优化之前,先在计算机上安装Gentoo Linux;或者学习一下“运行时分派”(Intel的ICC编译器所做的)是什么。然后尝试在MS-DOS上运行Java,看看它有多可移植。接着尝试对“纯”Java代码进行基准测试(不使用充满AOT编译本地代码的库;也不先将其AOT编译成字节码)。最后,尝试摊销短暂小型实用程序上不必要的性能灾难(在这种情况下,AOT程序甚至在你加载巨大的JIT编译器之前就已完成)。 - Brendan
@Brendan 显然,如果JIT没有像AOT那样多的时间,那么实现相同的结果在物理上是不可能的。这已经在答案中涵盖了。你太忙于讲故事和对抗JIT的教条斗争,以至于混淆了概念、实现和边缘情况。让我们在这里结束吧。 - Mani Gandham

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