Java 8个默认接口方法能否被JIT编译器内联?
Can Java 8 default interface methods be inlined by the JIT compiler?
我正在为 JITWatch 中的一段代码 运行 分析 HotSpot 日志以获得基准,并注意到许多方法调用由于“无静态绑定”而未被内联。这些似乎只发生在调用默认接口方法时。
我的问题是默认接口方法是否会阻止 JIT 编译器内联它们的调用?
interface A {
default double a() {
return Math.random();
}
}
interface B extends A {
default double b() {
return a();
}
}
class C implements B {
public double c() {
double c = 0;
for (int i = 0; i < 1_000_000; ++i) {
c += b();
}
return c;
}
public static void main(String[] args) {
System.out.println(new C().c());
}
}
在 JITWatch 中进一步检查后,这个问题似乎与调用其他默认接口方法的默认接口方法有关。鉴于“无静态绑定”消息,这将更有意义。
它是内联的。这是一个例子:
public class DefaultInline {
public static void main(String[] args) {
System.out.println(callMe());
}
static int callMe(){
A instance = new A(){};
int x = 0;
for (int i = 0; i < 1_000_000; ++i) {
x += (int)instance.myRandom();
}
return x;
}
interface A {
default double myRandom() {
return Math.random();
}
}
}
运行 它与:
java -XX:+UnlockDiagnosticVMOptions
-XX:+PrintInlining
-XX:CICompilerCount=2
DefaultInline.java
并看到一行包含:
@ 20 zero.x.so.DefaultInline$A::myRandom (4 bytes) inline
至于“无静态绑定”,它存在于 here, notice that this is in C1
. Because calling the method myRandom
compiles with invokeInterface
(and you can look above 方法的类型中 C1
将内联),C1 compiler
不会将其内联(至于我理解代码),但是 C2
会。
Eugene 的示例表明可以内联默认方法。
事实上,我认为内联的标准应该与任何其他非静态方法相同。
- 要内联的代码大小必须小于可调阈值。
- 该方法不得被 class 或接口的任何(当前加载的)子class 中的方法覆盖。
在您的示例中,我认为内联应该是可能的,假设这是示例中涉及的所有代码。
但是,在此处使用的特定 JIT 中,/ 可能存在其他限制。例如,调用另一个默认方法的默认方法可能是一种边缘情况,这种情况非常罕见,因此被认为不值得支持。另一种可能的解释是 C1 编译器没有进行深度 单态调度 分析/优化。
另一方面,这 可能是 过早的优化...除非您的性能分析已确定代码中的特定热点,其中内联可能会产生重大影响区别。通常,最好的策略是将其留给编译器。如果您对代码进行微优化以针对给定的 Java 版本提供最佳性能,则很有可能在更改为较新版本时需要重做这些工作。
我正在为 JITWatch 中的一段代码 运行 分析 HotSpot 日志以获得基准,并注意到许多方法调用由于“无静态绑定”而未被内联。这些似乎只发生在调用默认接口方法时。
我的问题是默认接口方法是否会阻止 JIT 编译器内联它们的调用?
interface A {
default double a() {
return Math.random();
}
}
interface B extends A {
default double b() {
return a();
}
}
class C implements B {
public double c() {
double c = 0;
for (int i = 0; i < 1_000_000; ++i) {
c += b();
}
return c;
}
public static void main(String[] args) {
System.out.println(new C().c());
}
}
在 JITWatch 中进一步检查后,这个问题似乎与调用其他默认接口方法的默认接口方法有关。鉴于“无静态绑定”消息,这将更有意义。
它是内联的。这是一个例子:
public class DefaultInline {
public static void main(String[] args) {
System.out.println(callMe());
}
static int callMe(){
A instance = new A(){};
int x = 0;
for (int i = 0; i < 1_000_000; ++i) {
x += (int)instance.myRandom();
}
return x;
}
interface A {
default double myRandom() {
return Math.random();
}
}
}
运行 它与:
java -XX:+UnlockDiagnosticVMOptions
-XX:+PrintInlining
-XX:CICompilerCount=2
DefaultInline.java
并看到一行包含:
@ 20 zero.x.so.DefaultInline$A::myRandom (4 bytes) inline
至于“无静态绑定”,它存在于 here, notice that this is in C1
. Because calling the method myRandom
compiles with invokeInterface
(and you can look above 方法的类型中 C1
将内联),C1 compiler
不会将其内联(至于我理解代码),但是 C2
会。
Eugene 的示例表明可以内联默认方法。
事实上,我认为内联的标准应该与任何其他非静态方法相同。
- 要内联的代码大小必须小于可调阈值。
- 该方法不得被 class 或接口的任何(当前加载的)子class 中的方法覆盖。
在您的示例中,我认为内联应该是可能的,假设这是示例中涉及的所有代码。
但是,在此处使用的特定 JIT 中,/ 可能存在其他限制。例如,调用另一个默认方法的默认方法可能是一种边缘情况,这种情况非常罕见,因此被认为不值得支持。另一种可能的解释是 C1 编译器没有进行深度 单态调度 分析/优化。
另一方面,这 可能是 过早的优化...除非您的性能分析已确定代码中的特定热点,其中内联可能会产生重大影响区别。通常,最好的策略是将其留给编译器。如果您对代码进行微优化以针对给定的 Java 版本提供最佳性能,则很有可能在更改为较新版本时需要重做这些工作。