为什么将varargs的方法优化为一系列单态调用,只有它是静态的?

Fod*_*der 6 java optimization jvm variadic-functions

在vJUG24,其中一个主题是JVM性能.

幻灯片可以在这里找到.

他有一个例子:

static void log(Object... args) {
    for(Object arg : args) {
        System.out.println(arg);
    }
}
Run Code Online (Sandbox Code Playgroud)

被称为通过(不能正确读取幻灯片,但它是相似的):

void doSomething() {
    log("foo", 4, new Object());
}
Run Code Online (Sandbox Code Playgroud)

他说因为它是一种静态方法,所以可以通过内联它来优化它:

void doSomething() {
    System.out.println("foo");
    System.out.println(new Integer(4).toString());
    System.out.println(new Object().toString());
}
Run Code Online (Sandbox Code Playgroud)

为什么log方法对于JVM进行此优化是静态的很重要?

apa*_*gin 6

演示文稿不是很精确,或者说你做得不对.

事实上,即使使用varargs ,JVM 也可以内联非静态方法.而且,Object[]在某些情况下它可以消除相应阵列的分配.不幸的是,当vararg方法使用for循环遍历数组时,它不会这样做.

我做了以下JMH基准来验证理论并使用GC profiler(-prof gc)运行它.

package bench;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.infra.Blackhole;

public class VarArgs {

    @Benchmark
    public void inlineNonStatic(Blackhole bh) {
        inlineNonStaticVA(bh, "foo", 4, new Object());
    }

    @Benchmark
    public void inlineStatic(Blackhole bh) {
        inlineStaticVA(bh, "foo", 4, new Object());
    }

    @Benchmark
    public void loopNonStatic(Blackhole bh) {
        loopNonStaticVA(bh, "foo", 4, new Object());
    }

    @Benchmark
    public void loopStatic(Blackhole bh) {
        loopStaticVA(bh, "foo", 4, new Object());
    }

    public void inlineNonStaticVA(Blackhole bh, Object... args) {
        if (args.length > 0) bh.consume(args[0]);
        if (args.length > 1) bh.consume(args[1]);
        if (args.length > 2) bh.consume(args[2]);
        if (args.length > 3) bh.consume(args[3]);
    }

    public static void inlineStaticVA(Blackhole bh, Object... args) {
        if (args.length > 0) bh.consume(args[0]);
        if (args.length > 1) bh.consume(args[1]);
        if (args.length > 2) bh.consume(args[2]);
        if (args.length > 3) bh.consume(args[3]);
    }

    public void loopNonStaticVA(Blackhole bh, Object... args) {
        for (Object arg : args) {
            bh.consume(arg);
        }
    }

    public static void loopStaticVA(Blackhole bh, Object... args) {
        for (Object arg : args) {
            bh.consume(arg);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining 显示所有4个变体都成功内联到调用者:

    @ 28   bench.VarArgs::inlineNonStaticVA (52 bytes)   inline (hot)
    @ 27   bench.VarArgs::inlineStaticVA (52 bytes)   inline (hot)
    @ 28   bench.VarArgs::loopNonStaticVA (35 bytes)   inline (hot)
    @ 27   bench.VarArgs::loopStaticVA (33 bytes)   inline (hot)
Run Code Online (Sandbox Code Playgroud)

结果证实,调用静态方法与非静态方法之间没有性能差异.

Benchmark                Mode  Cnt   Score   Error  Units
VarArgs.inlineNonStatic  avgt   20   9,606 ± 0,076  ns/op
VarArgs.inlineStatic     avgt   20   9,604 ± 0,040  ns/op
VarArgs.loopNonStatic    avgt   20  14,188 ± 0,154  ns/op
VarArgs.loopStatic       avgt   20  14,147 ± 0,059  ns/op
Run Code Online (Sandbox Code Playgroud)

但是,GC事件探查器指示Object[]loop*方法分配了vararg 数组,但没有为inline*方法分配.

Benchmark                                    Mode  Cnt     Score     Error   Units
VarArgs.inlineNonStatic:·gc.alloc.rate.norm  avgt   20    16,000 ±   0,001    B/op
VarArgs.inlineStatic:·gc.alloc.rate.norm     avgt   20    16,000 ±   0,001    B/op
VarArgs.loopNonStatic:·gc.alloc.rate.norm    avgt   20    48,000 ±   0,001    B/op
VarArgs.loopStatic:·gc.alloc.rate.norm       avgt   20    48,000 ±   0,001    B/op
Run Code Online (Sandbox Code Playgroud)

我想,最初的观点是静态方法总是单形的.但是,如果特定调用站点中没有太多实际接收器,JVM也可以内联多态方法.