【问题标题】:Why getSum does not get inlined by hotspot jvm?为什么 getSum 不会被热点 jvm 内联?
【发布时间】:2018-08-19 10:31:54
【问题描述】:

这是我试图从Java Performance: The Definitive Guide, Page 97 复制的关于逃逸分析 主题的示例。这可能是应该发生的事情:

  1. getSum() 必须足够热,并且必须使用适当的 JVM 参数将其内联到调用者 main()
  2. 由于listsum 变量都不会从main() 方法中逃脱,它们可以标记为NoEscape,因此JVM 可以为它们使用堆栈分配而不是堆分配。

但我通过jitwatch 运行它,结果显示getSum() 编译成本机程序集并且没有内联到main()。更不用说因此堆栈分配也没有发生。

我在这里做错了什么? (我已经把整个代码和热点日志here放好了。)

代码如下:

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.stream.IntStream;

public class EscapeAnalysisTest {

    private static class Sum {

        private BigInteger sum;
        private int n;

        Sum(int n) {
            this.n = n;
        }

        synchronized final BigInteger getSum() {
            if (sum == null) {
                sum = BigInteger.ZERO;
                for (int i = 0; i < n; i++) {
                    sum = sum.add(BigInteger.valueOf(i));
                }
            }
            return sum;
        }

    }

    public static void main(String[] args) {
        ArrayList<BigInteger> list = new ArrayList<>();
        for (int i = 1; i < 1000; i++) {
            Sum sum = new Sum(i);
            list.add(sum.getSum());
        }
        System.out.println(list.get(list.size() - 1));
    }

}

我使用的JVM参数:

-server
-verbose:gc
-XX:+UnlockDiagnosticVMOptions
-XX:+TraceClassLoading
-XX:MaxInlineSize=60
-XX:+PrintAssembly
-XX:+LogCompilation

【问题讨论】:

  • synchronized 可能是一个原因。
  • @Henry 我试图从书中复制,但我也尝试过不同步但仍然无法内联。
  • 很有趣,但我在你的日志中看到这样的条目task_queued compile_id='37' compile_kind='osr',好像osr 被踢了
  • 是的@Eugene,getSum 方法被编译到本机程序集中。但我期待别的东西(我在问题中描述过)
  • 你的循环运行在main方法中,所以只能通过OSR切换到优化版本。众所周知,这是有局限性的。

标签: java jit jvm-hotspot escape-analysis


【解决方案1】:

为了知道为什么有些东西是内联的,你可以在编译日志中查看inline_successinline_fail标签。

但是,即使要内联某些内容,也必须编译调用者,在您的情况下,您希望在 main 方法中进行内联,因此发生这种情况的唯一方法是堆栈替换 (OSR)。查看您的日志,您可以看到一些 OSR 编译,但没有一个 main 方法:您的 main 方法中根本没有足够的工作。

您可以通过增加 for 循环的迭代次数来解决此问题。通过将其增加到 100_000,我得到了第一个 OSR 编译。

对于这样一个小例子,查看-XX:+PrintCompilation -XX:+PrintInlining 而不是整个LogCompilation 输出,我看到了:

@ 27   EscapeAnalysisTest$Sum::getSum (51 bytes)   inlining prohibited by policy

这不是很有帮助...但是稍微看一下 HotSpot 源代码就会发现,这可能是因为有一项策略阻止 C1 编译到内联由 C2 编译的 OSR 方法。 无论如何,看看 C1 编译完成的内联并不是那么有趣。

添加更多循环迭代(1_000_000Sum 的参数取模以减少运行时间)使我们的 C2 OSR 为 main,其中:

@31   EscapeAnalysisTest$Sum::getSum (51 bytes)   already compiled into a big method  

C2 策略的那一部分是相当自我描述的,由InlineSmallCode 标志控制:-XX:InlineSmallCode=4k 告诉 HotSpot,“大方法”的阈值是 4kB 本机代码。在我的机器上,这足以让 getSum 内联:

  14206   45 %     4       EscapeAnalysisTest::main @ 10 (61 bytes)
                              @ 25   EscapeAnalysisTest$Sum::<init> (10 bytes)   inline (hot)
                                @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
              s               @ 31   EscapeAnalysisTest$Sum::getSum (51 bytes)   inline (hot)
                                @ 31   java.math.BigInteger::valueOf (62 bytes)   inline (hot)
                                  @ 58   java.math.BigInteger::<init> (77 bytes)   inline (hot)
                                    @ 1   java.lang.Number::<init> (5 bytes)   inline (hot)
                                      @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
                                @ 34   java.math.BigInteger::add (123 bytes)   inline (hot)
                                  @ 41   java.math.BigInteger::add (215 bytes)   inline (hot)
                                  @ 48   java.math.BigInteger::<init> (38 bytes)   inline (hot)
                                    @ 1   java.lang.Number::<init> (5 bytes)   inline (hot)
                                      @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
                              @ 34   java.util.ArrayList::add (29 bytes)   inline (hot)
                                @ 7   java.util.ArrayList::ensureCapacityInternal (13 bytes)   inline (hot)
                                  @ 6   java.util.ArrayList::calculateCapacity (16 bytes)   inline (hot)
                                  @ 9   java.util.ArrayList::ensureExplicitCapacity (26 bytes)   inline (hot)
                                    @ 22   java.util.ArrayList::grow (45 bytes)   too big

(请注意,我从来不用MaxInlineSize

以下是修改后的循环供参考:

for (int i = 1; i < 1_000_000; i++) {
  Sum sum = new Sum(i % 10_000);
  list.add(sum.getSum());
}

【讨论】:

  • 这个很有趣:inlining prohibited by policy 我已经看过源代码,所以如果 JIT 知道C2 将使用OSR,它甚至不会将它内联到C1,因为有可能没有意义。我认为这是 3-td 层中的另一种“不进入”形式,当 4-th 已经将此方法编译成“更好”的东西时
猜你喜欢
  • 1970-01-01
  • 2012-11-20
  • 1970-01-01
  • 2013-04-10
  • 1970-01-01
  • 2010-11-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多