java - 为什么一个采用可变参数的方法只有在它是静态的情况下才能被优化为一系列单态调用?

标签 java optimization jvm variadic-functions

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

可以找到幻灯片here .

他有一个例子:

static void log(Object... args) {
    for(Object arg : args) {
        System.out.println(arg);
    }
}

这是通过调用的(不能完全正确地阅读幻灯片,但它是相似的):

void doSomething() {
    log("foo", 4, new Object());
}

他说因为是静态方法,可以这样内联优化:

void doSomething() {
    System.out.println("foo");
    System.out.println(new Integer(4).toString());
    System.out.println(new Object().toString());
}

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

最佳答案

要么是表述不够准确,要么是你表述不正确。

事实上,JVM 可以内联非静态方法,即使使用可变参数也是如此。此外,在某些情况下,它可以消除分配相应的 Object[] 数组。不幸的是,当可变参数方法使用 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);
        }
    }
}

-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)

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

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

但是,GC 分析器指示可变参数 Object[] 数组是为 loop* 方法分配的,而不是为 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

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

关于java - 为什么一个采用可变参数的方法只有在它是静态的情况下才能被优化为一系列单态调用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39735258/

相关文章:

java - 在 JTable 中鼠标悬停时创建信息面板?工具提示可能不够

c++ - 由于编译器优化,代码运行缓慢

java - 迁移到 64 位 JVM 的经验

java - 如何优化 JVM 以利用更少的资源

java - IntStream Java 出现次数

java基础垃圾回收

java - 如何在同一包中的其他文件中使用对象数组?

mysql - 针对大型查询优化 MySql 数据库

javascript - 优化Javascript方法来填充或清空数组

java - sun.security.ssl.allowUnsafeRenegotiation