【问题标题】:Limits of JVM JIT-Compiler OptimizationsJVM JIT 编译器优化的限制
【发布时间】:2023-11-05 18:04:02
【问题描述】:

我目前正在研究从自定义 DSL 到 Java 的编译器,同时在此过程中执行一些初步的性能优化。我最大的问题是,没有关于 JIT 编译器将在优化(通过)方面做什么或他们将做什么扩展(例如,复杂的死代码消除,参见下面的示例)的学术资源。有许多博客文章说由于某些时间限制,JIT-Compiler 不会做 AOT-Compiler 会做的所有优化,但没有人提到这实际上意味着什么。有一般的经验法则吗?我需要深入研究吗? OpenJDK C++ 源代码来理解这一点?有没有这方面的研究?如果没有,是否有关于 JVM JIT 进行何种优化的可靠资源?我找到的最新资源是关于 Java 5 的,它已经过时了 (http://www.oracle.com/technetwork/java/5-136747.html)

这是一个“复杂的死代码消除”场景的简化示例,我发现 JVM JIT 无法消除该场景,因为变量 cells_S_S_S 未在任何地方使用(请记住,这是自动生成的代码):

List<List<List<Cell>>> cells_S_S_S = new ArrayList<>(pla_S.size());
...
for (int pla_S_itr_45 = 0; pla_S_itr_45 < pla_S_size_45; ++pla_S_itr_45) {
        ...
        List<List<Cell>> cells_S_S = new ArrayList<>(tmpVarIf20_S.size());
        for (int tmpVarIf20_S_itr_44 = 0; tmpVarIf20_S_itr_44 < tmpVarIf20_S_size_44; ++tmpVarIf20_S_itr_44) {
            ...
            List<Cell> cells_S = _state.getCells();
            ...
            cells_S_S.add(cells_S);
        }
        ...
        cells_S_S_S.add(cells_S_S);
    }

这种“嵌套的死代码”并没有被消除,这让我自己执行了上述优化。

简而言之:我想知道 JVM JIT 的能力,以便我可以将自己的优化过程集中在正确的领域。

【问题讨论】:

  • 也许这个链接很有趣:wiki.openjdk.java.net/display/HotSpot/PerformanceTacticIndex(wiki 站点今天加载速度很慢,所以您可以使用归档站点)。
  • 感谢您的意见!可悲的是,这根本没有帮助我,因为该页面上没有确切的信息或任何参考资料。它看起来只是一个巨大的黑匣子。
  • 虽然这个问题很有趣,但它在 Stack Overflow 上是题外话,因为它太宽泛和/或需要场外资源。
  • 这个问题是关于 JIT 编译器实际做什么的。这是如何广泛的?我的问题的另一部分只是提供上下文/示例。不过,我理解您对场外资源的评论。
  • 逃逸分析有助于消除堆分配。我的陈述是关于堆中的实际分配(即没有消除)。我已经告诉过为什么在您的情况下没有消除 ArrayList 的分配 - 因为它包含一个非常量大小的数组。见escape.cpp

标签: java optimization jvm compiler-optimization jit


【解决方案1】:

我想知道 JVM JIT 的能力,以便我可以将自己的优化传递集中在正确的领域。

简单的回答:不要。

你必须考虑两件事:

  1. 是的,Oracle HotSpot JVM JIT 引擎在多次传递期间执行广泛的优化。您列出的其中一些(死代码消除、内联、去虚拟化等)等等。
    重要的是要注意 JIT 引擎的行为不是 标准化和其他公司的 JVM 的行为方式不同。我从未见过全面描述 HotSpot 如何在内部做出决策或支持的优化列表的文档,我非常怀疑这样的文档是否存在(不是来自 Oracle,不是来自社区)。您可以深入研究 HotSpot VM 的源代码,但是:
  2. HotSpot 不断尝试确定您的应用程序中的热点,并以一种非确定性的方式决定需要 jitted 什么,如何在当前上下文中执行它(对于更热的方法,它是有意义的应用代价高昂的优化)以及哪些 jitted 方法需要被丢弃并可能重新编译。
    您的应用程序的状态不稳定,JIT 引擎会不断决定如何处理它,并根据当前环境选择应用哪组优化。

您正在尝试针对特定的 JIT 行为优化从 DSL 转译的代码,但是您所做的每个假设都可能对一个特定的运行有效,但对另一个运行无效。或者一段时间后不再有效,当 jit 引擎决定删除方法的 jit 版本以释放内存或以不同的结果再次编译时。

JIT 和 AOT 之间的唯一区别在于,后者没有时间限制,因此您尽量生成最好的代码,以衡量质量。

【讨论】:

  • 我明白你关于 JIT 的非确定性的观点。但就目前而言,我很惊讶根本没有任何关于此的科学研究/论文,甚至没有针对特定的 Java 版本/OpenJDK 版本。此外,我不想优化 JIT 以生成更好的代码,而是优化 JIT 本身无法优化的程序部分(请参阅主帖上的评论链,了解为什么 JIT 不能,死代码-optimize”我的例子),但这似乎是一个更一般的优化问题,即使 AOT 编译器也会遇到。
  • 我同意,几年前我搜索过这类文档,但找不到任何全面的信息,关于 JIT 细节的最有趣的部分分散在大量博客和文章中。我的猜测是,考虑到贡献者的数量很少(主要是在运行时),他们从不觉得有必要公开描述内部设计和他们过去做出的选择的文档。
【解决方案2】:

JIT 优化实际上比全程序优化强大得多。 JIT 优化会临时调整代码以获得最佳性能,并根据对代码的假设进行极其不安全的优化。例如,JIT 优化器可以预先计算某些内容,然后在假设错误时将其回滚。 JIT 内联器通过内联方法帮助 JIT 优化器,以便它们可以适应特定的调用者。

【讨论】: