Java 8 默认接口方法能否被JIT编译器内联?

7

我分析了在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 进一步检查,似乎这个问题涉及默认接口方法调用其他默认接口方法。考虑到“没有静态绑定”消息,这更有意义。

看这里:https://github.com/openjdk/jdk/blob/master/src/hotspot/share/c1/c1_GraphBuilder.cpp#L1971 或许可以帮到你... - JCWasmx86
JIT可以内联默认方法,我确实看到它这样做了。如果您询问特定代码的问题,请提供MCVE(最小可重现示例)。 - apangin
顺便提一下,“无静态绑定”消息仅适用于C1。 - apangin
@apangin 我已经添加了一个例子。 - Hao Zhang
2个回答

2

尤金的例子表明默认方法可以内联。

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

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

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

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

相反的情况是,这可能是过早的优化...除非您的性能分析确定了代码中的特定热点,其中内联可以产生显着差异。通常,最好将此留给编译器。如果您微调代码以在给定的Java版本下获得最佳性能,则有很大机会在切换到新版本时需要重新进行操纵。


我明白了。我确实看到,在大多数情况下,当C1编译器无法内联默认接口方法时,C2编译器会在稍后内联它们以进行“热点”优化。这只有在我手动查看JITWatch中各种方法的内联报告时才会显现出来,而不是仅仅浏览其“建议”窗口。 - Hao Zhang
@HaoZhang C1将始终无法内联默认方法,正如我的答案所示,但我不认为你理解了这一点,是吗? - Eugene
@Eugene 仍然存在一些情况,C2由于被调用者的大小而无法内联它们,但我知道这完全是可配置的。我们是否会配置我们的JVM来增加这个限制还有待讨论。 - Hao Zhang

2
它被内联了。这里是一个例子:
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

关于“无静态绑定”,它在这里present,请注意这是在C1中。因为调用方法myRandom编译时使用invokeInterface(您可以在上面查看C1将内联的方法类型),C1编译器不会内联它(就我理解的代码而言),但C2会。

这种内联是否发生取决于接口A扩展接口B并且myRandom()调用接口B中的默认方法?请参阅我的原始帖子以获取示例。 - Hao Zhang
@HaoZhang 但这很容易找出来,对吧?只需对此示例进行轻微重构即可。 - Eugene

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