【问题标题】:What can a JIT compiler do that an AOT compiler cannot?JIT 编译器能做什么而 AOT 编译器不能?
【发布时间】:2019-03-01 13:48:03
【问题描述】:

即时 (JIT) 编译器可以根据 Ahead-of-Time (AOT) 编译器无法获得的运行时信息优化程序。

这个运行时信息最明显的例子是目标平台,例如运行程序的确切 CPU,或任何可用的加速器,例如 GPU。这就是 OpenCL 是 JIT 编译的意义。

但假设我们提前知道目标平台是什么:我们知道哪些 SIMD 扩展可用,等等。JIT 编译器可以利用哪些其他运行时信息,而 AOT 编译器不可用?

HotSpot 风格的 JIT 编译器会自动优化程序的热点……但 AOT 编译器不能只优化整个程序、热点和所有内容吗?

我想要一些 JIT 编译器可以执行而 AOT 编译器不能执行的特定优化的示例。如果您能提供任何证据证明此类优化在“现实世界”场景中的有效性,则可获得奖励积分。

【问题讨论】:

  • @AnubhavSrivastava 感谢您的链接。这是一个类似的问题,但评分最高的答案和接受的答案都没有真正回答我的问题。不过,在其他答案中有几个仅 JIT 优化的示例:跨库优化和使用跟踪树进行动态内联。我很想知道这些在实践中有多大的不同。
  • 反射是一个臭名昭著的问题,不能静态地从一个字符串中确定需要什么类型。

标签: optimization compiler-construction compiler-optimization jit


【解决方案1】:

JIT 可以基于运行时信息进行优化,这会导致更严格的边界条件在编译时无法证明。例子:

  • 可以看到内存位置没有别名(因为所采用的代码路径从未别名),因此将变量保存在寄存器中;
  • 它可以消除对永远不会发生的条件的测试(例如,基于参数的当前值);
  • 它可以访问完整的程序,并且可以在它认为合适的地方内联代码;
  • 它可以在运行时根据具体的使用模式进行分支预测,从而达到最优。

内联主要也对现代编译器/链接器的链接时间优化开放,但如果为了以防万一而在整个代码中应用,可能会导致令人望而却步的代码膨胀;在运行时,它可以在需要的地方应用。

如果程序编译两次,中间有一次测试运行,则可以使用普通编译器改进分支预测;在第一次运行中,代码被检测,以便它生成分析数据,这些数据用于生产编译运行以优化分支预测。如果测试运行不是典型的(并且生成典型的测试数据并不总是那么容易,或者使用模式可能会在程序的生命周期内发生变化),那么预测也不是最佳的。

此外,使用静态编译进行链接时间和运行时数据优化都需要在构建过程中付出巨大的努力(在某种程度上,我一生中工作过的 10 个左右的地方都没有看到它们在生产中使用) ;使用 JIT,它们默认处于打开状态。

【讨论】:

  • 谢谢,这正是我所追求的。
【解决方案2】:

JIT 编译器能做什么而 AOT 编译器不能?

理论上;什么都没有,因为 AOT 编译器可以根据需要将 JIT 编译器插入到生成的代码中(和/或可以生成自修改代码,生成 123 个替代版本并根据运行时信息选择要使用的版本,... )。

在实践中; AOT 编译器受到编译器设计者想要处理的复杂程度、它正在编译的语言以及编译器的使用方式的限制。例如,一些编译器(英特尔的 ICC)会生成多个版本的代码,并(在运行时)根据运行的 CPU 决定使用哪个版本,但大多数编译器并不是为此而设计的;许多语言没有提供任何方法来控制“局部性”(并减少 TLB 未命中和缓存未命中的机会);并且通常编译器的使用方式会产生阻碍优化的障碍(例如,单独的“编译单元”/稍后链接在一起的目标文件,可能包括动态链接,其中 AOT 编译器不可能进行整个程序优化,并且只能可以单独优化零件)。所有这些都是实现细节,而不是 AOT 的限制。

换句话说;在实践中,“AOT 与 JIT”是实现的比较,而不是“AOT 与 JIT”本身的真正比较;实际上,由于实现细节,AOT 的性能很差,而 JIT 的性能比差的性能要差得多,因为 JIT 本身很糟糕(昂贵的优化根本不可行,因为它们是在运行时完成的); JIT 看起来“几乎一样好”的唯一原因是它“几乎和坏一样好”。

【讨论】:

  • 如果 AOT 编译器将 JIT 编译器插入到生成的代码中,我想我们可以说结果是 JIT 编译的。问题的重点是问它为什么会这样做——JIT 编译器能做什么而 AOT 编译器不能自己做? Peter A. Schneider 在他的回答中给出了一些示例,而不仅仅是实现细节。
  • @c--: 如果 AOT 编译器实际上插入了 JIT(对于程序的某些部分,它认为它是有益的),那么它实际上与使用其他技巧的 AOT 编译代码无法区分得到同样的好处。换句话说,它仍然是 AOT 编译的,之后的一切都在讨论假设的实现细节。对于 Peter A. Schneider 的回答,我可以找到一个没有错误的示例 - AOT 编译器可以完成所有列出的事情(无需在 AOT 编译代码中插入 JIT),并且比使用 JIT 更有效地完成这一切。
  • 鉴于 AOT 不知道“参数的当前值”。 AOT 能做的最好的事情就是消除基于不断传播的无法访问的代码,确定吗?
  • @c--:我很想知道人们怎么会愚蠢到相信“测试/s 来确定(基于当前值)是否可以消除测试”是有道理的。他们是否认为如果没有额外的相等或更差的测试,您可以决定是否可以/不能消除测试?他们是否意识到现代 CPU 中的分支预测使得消除测试成为破坏性能的徒劳练习(即使没有“测试 / s 可能避免测试”的白痴)?
  • 没有必要说人们愚蠢或抱怨“妄想”。这个问题有两个部分:(a)JIT 编译器能做什么而 AOT 编译器不能; (b) 有什么证据表明 (a) 的答案在“现实世界”使用中实际上是有效的。提供 (a) 的答案是有效的,但承认它们在实践中很少有帮助。
【解决方案3】:

一个优点是 JIT 编译器可以持续分析代码并优化输出,例如对齐/取消对齐某些代码块、取消优化某些函数、重新排序分支以减少错误预测...

当然,AoT 编译器也可以进行配置文件引导优化,但它们仅限于开发人员和测试人员执行的测试用例,这可能无法反映真实输入的动态特性

例如,当 Android 在 Kitkat 中引入 ART 到 Nougat 和更高版本中的混合方法时,Android 已经从 AoT 回归,其中应用程序的某些部分提前快速编译,优化较少,然后在运行后配置文件结果将用于在手机充电时再次优化应用程序

Android 7.0 Nougat 向 ART 引入了带有代码分析功能的 JIT 编译器,这使它能够在 Android 应用程序运行时不断提高它们的性能。 JIT 编译器补充了 ART 当前的 Ahead of Time 编译器,有助于提高运行时性能。[9]

https://en.wikipedia.org/wiki/Android_Runtime

一些相关问题:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-01-23
    • 2011-11-27
    • 2023-04-02
    • 2020-06-20
    • 2017-03-13
    • 1970-01-01
    • 2010-09-11
    • 1970-01-01
    相关资源
    最近更新 更多