在 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/