为什么JVM中没有提示JIT编译器的基础设施?

5

我正在审查一份涉及JIT内联语义的会议演示文稿,作者指出了奇怪的行为(当然,只是乍一看很奇怪)- C2比C1慢,因为它由于过多的内联深度而无法内联方法。这可以用以下示例表示:

public static int multipleByTwo(int x) {
    return x * 2;
}

public static void entrypoint() {
    int sum = 0;
    for (int i = 0; i < 10_000_000; i++) {
        // due to some arbitrary cause, multiplyByTwo doesn't get inlined
        sum += multiplyByTwo(i);
    }
}

作为一名程序员,我可能知道编译器不知道的优化领域。例如,如果强制将multiplyByTwo内联,那么有很多优化可能性,但由于各种限制(例如方法大小或内联深度),它可能被省略。为什么没有办法告诉编译器“嘿,我非常确定你应该更喜欢内联这个方法而不是其他的”?我相信我不是第一个想到这个问题的人,也有关于不实现这个功能的讨论 - 为什么呢?
附注:请注意,我谈论的是提示而不是指令;我明白后者的选择会带来更多的伤害而不是好处。

AI算法用于确定何时以及如何通过JIT进行运行时优化,例如方法内联,这确实非常难以理解。 但是将方法设置为“final”可以提示Java将其内联。据我所知,没有任何方法可以确保告诉JIT内联方法。 - Ankush G
1
假设最大内联深度更大,这个特定问题可能会消失 - 因此问题是为什么有最大深度;或者,为什么不能使用JVM参数增加该深度。更一般地说,问题是“为什么当Y时JIT停止执行X优化”:因为JIT的作者无法花费无限的时间确保处理每种可能的情况。在大多数情况下,它运行得相当不错。 - Andy Turner
@vvtx 是的,我知道我不能支配编译器(至少在现代JVM中是这样)。我的问题是“为什么没有人实现这个?”而不是“我如何强制编译器按照我的意愿进行内联?” - Etki
@AndyTurner 是的,我知道关于最大内联深度的问题。但是开发人员可以提示编译器应该内联一个更深的方法,而不是从顶部开始内联(保持指定的深度),如果他确信这将有益于整体性能。整个问题并不是关于内联本身,它只是展示了一个开发人员干预可能会带来性能收益的例子,但由于未知原因,这种干预是不可能的 - 我想知道那个原因。 - Etki
1
我的评论并不是关于内联本身的。它指出了JIT尝试优化的实际限制,而且事实上在绝大多数情况下它都运行得非常好,这就消除了提示机制的额外复杂性。 - Andy Turner
@AndyTurner 对不起,我走神了,没有认真阅读,我的错。 - Etki
2个回答

16

实际上,存在一种基础设施来控制HotSpot JVM编译器。

1. 编译器命令文件

您可以使用-XX:CompileCommandFile= JVM选项指定包含编译器命令的文件。这些命令用于强制内联、排除方法编译、设置每个方法选项(例如MaxNodeLimit)等。可用命令的完整列表可以在此处找到。

一个示例编译器命令文件可能如下所示

inline java.util.ArrayList::add
exclude *::<clinit>
print com.example.MyClass::*

2. 注解

JDK特定的注解是控制JVM优化的另一种方式。有一些注解是HotSpot JVM知道的,例如:

注意: 所有这些机制都不是标准的。它们仅适用于OpenJDK和Oracle JDK。没有一种标准的方法可以提示JVM编译器,因为有许多JVM实现具有完全不同的编译策略。特别地,有些JVM根本没有JIT编译。


非常棒的答案,感谢提供信息。 我想补充一下,您提供的链接指向源代码,没有选项描述,但在Java命令选项文档中更全面地涵盖了(https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html) 如果您不介意,我会编辑答案,使其更完整。 - Bogdan Mart

2

好的,这里有一些提示可以告诉JVM的优化器这个方法是一个很好的内联候选者:

  • 它是staticprivate的,即不可重写
  • 它非常短
  • 它在循环中被多次调用

实际上,你认为这个方法是一个很好的内联候选者的假设基于相同的技术证据,因此添加一个提示,即你认为这是一个好的内联候选者,不会添加任何新信息,只会产生冗余。

因此,如果JVM出于任何原因仍然不内联该方法,尽管所有这些技术属性都支持内联,那么没有理由假设一个非强制性的、非技术性的提示,最有可能源自相同的技术属性,会改变JVM的决策。

您可以选择任何潜在的原因,防止某些问题、过于严格的限制,甚至是有缺陷的JVM实现,在任何情况下,您会发现相同的原因也适用于具有您提示的方法,即使它被证明是没有根据的原因,也适用于没有您提示的方法。因此,在后一种情况下,显而易见的解决方案是修复JVM中的缺陷,而不是添加一个通用提示机制。

通用提示机制尤其值得怀疑,因为代码应该是平台无关的。如果您在已知环境中查看特定运行情况与特定JVM实现的运行情况不同。例如,HotSpot支持-XX:CompileCommand选项。因此,在您的情况下,您可以使用-XX:CompileCommand=inline,your/class/Name,multiplyByTwo来尝试说服JVM内联该方法。当然,正确的拼写很重要。在您的问题中,该方法一次被命名为multipleByTwo,然后是multiplyByTwo...


我知道内联规则(顺便说一句,我听说很多次静态/最终修饰符本身并不起任何作用,内联用于所有未被覆盖的方法,静态/最终只是使覆盖变得不可能)。这个例子很简单,真的可以在没有任何提示的情况下解决,但是有许多可能的情况,当JVM需要体面的AI来弄清楚发生了什么,并且“在内部修复”是不可能的。 - Etki
这就是为什么这些属性都只是“提示”。JVM也可以内联可重写方法,因此它可以内联更大的方法或不在紧密循环中调用的方法。正如所说,这些选项都是提示,表明这是一个好的候选项,那么你提出的个人提示是什么?已知问题并不复杂,就是在评论中提到的最大内联深度。由于这是一个实现特定的问题,没有理由引入一个通用的提示功能来代替实现特定的选项。 - Holger
我不是在谈论内联本身。这只是一个例子,当开发人员可能比JVM更了解时,可能不是最好的例子。 - Etki
如果你不是在谈论内联,那么指明你实际上在谈论什么会很有用。此外,Java是一种平台无关的语言。你不知道你的代码将在哪种实际硬件、哪个操作系统或哪个特定的JVM实现上运行,但你认为你比未知的JVM实现更了解如何执行你的代码?好吧,我期待一个真正的例子... - Holger

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