为什么 getSum 没有被热点 jvm 内联?
Why getSum does not get inlined by hotspot jvm?
这是我试图从 Java Performance: The Definitive Guide, Page 97 中复制的关于 Escape Analysis 主题的示例。这可能是应该发生的事情:
getSum()
必须足够热,并且必须使用适当的 JVM 参数将其内联到调用方 main()
.
- 由于
list
和 sum
变量都没有从 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
为了知道为什么某些东西被内联或没有被内联,您可以在编译日志中查找 inline_success
和 inline_fail
标签。
然而,即使要内联某些内容,也必须编译调用者,在您的情况下,您希望在 main
方法中进行内联,所以唯一的方法就是 on-stack替换(OSR)。查看您的日志,您可以看到一些 OSR 编译,但是 main
方法的 none:您的 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_000
对 Sum
的参数取模以减少 运行 时间)让我们得到一个 main
的 C2 OSR:
@31 EscapeAnalysisTest$Sum::getSum (51 bytes) already compiled into a big method
C2 的那部分策略相当 self-descriptive 并且由 InlineSmallCode
标志控制:-XX:InlineSmallCode=4k
告诉 HotSpot "big method" 的阈值是 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());
}
这是我试图从 Java Performance: The Definitive Guide, Page 97 中复制的关于 Escape Analysis 主题的示例。这可能是应该发生的事情:
getSum()
必须足够热,并且必须使用适当的 JVM 参数将其内联到调用方main()
.- 由于
list
和sum
变量都没有从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
为了知道为什么某些东西被内联或没有被内联,您可以在编译日志中查找 inline_success
和 inline_fail
标签。
然而,即使要内联某些内容,也必须编译调用者,在您的情况下,您希望在 main
方法中进行内联,所以唯一的方法就是 on-stack替换(OSR)。查看您的日志,您可以看到一些 OSR 编译,但是 main
方法的 none:您的 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_000
对 Sum
的参数取模以减少 运行 时间)让我们得到一个 main
的 C2 OSR:
@31 EscapeAnalysisTest$Sum::getSum (51 bytes) already compiled into a big method
C2 的那部分策略相当 self-descriptive 并且由 InlineSmallCode
标志控制:-XX:InlineSmallCode=4k
告诉 HotSpot "big method" 的阈值是 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());
}