JIT 编译器可以内联 Java 8 默认接口方法吗?

问题描述

我正在分析 HotSpot 日志以获取在 JITWatch 中运行的一段代码的基准测试,并注意到由于“无静态绑定”而没有内联许多方法调用。这些似乎只发生在调用认接口方法时。

我的问题是认接口方法是否会阻止 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中进一步检查,似乎这个问题与调用其他认接口方法认接口方法有关。考虑到“无静态绑定”消息,这会更有意义。

解决方法

Eugene 的示例表明可以内联默认方法。

事实上,我认为内联的标准应该与任何其他非静态方法的标准相同。

  • 要内联的代码大小必须小于可调阈值。
  • 该方法不得被类或接口的任何(当前加载的)子类中的方法覆盖。

在您的示例中,我认为内联应该是可能的,假设这是示例中涉及的所有代码。

但是,此处使用的特定 JIT 中的 / 可能存在其他限制。例如,调用另一个默认方法的默认方法可能是一种非常罕见的边缘情况,以至于被认为不值得支持。另一种可能的解释是 C1 编译器没有进行深入的单态分派分析/优化。

另一方面,这可能是过早的优化……除非您的性能分析确定了代码中的特定热点,在该热点中内联可能会产生重大影响。通常,最好的策略是将其留给编译器。如果您对代码进行微优化以针对给定的 Java 版本提供最佳性能,则很有可能在更改到较新版本时需要重做这项工作。

,

它是内联的。下面是一个例子:

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中,注意它在C1中。因为调用方法 myRandom 使用 invokeInterface 编译(您可以查看 above 方法的类型 C1 将内联),C1 compiler 不会内联它(据我了解代码),但 C2 会。