Why should the method that varargs accepts be optimized into a series of monomorphic calls only if it is static?

In vJUG24, one of the topics was JVM performance .

Slides can be found here .

He had an example:

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

which was called through (cannot read the slide correctly, but it looks like):

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

He said that it was a static method, it could be optimized by inserting it like this:

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

Why is it important that the log method be static for the JVM for this optimization?

+5
source share
1 answer

Either the presentation was not entirely accurate, or you did not understand it.

In fact, the JVM can have built-in non-static methods, even with varargs. Moreover, it can exclude allocation of the corresponding Object[] array in certain cases. Unfortunately, it does not do this when the vararg method iterates through the array using a for loop.

I did the following JMH to test the theory and run it using the 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 shows that all 4 options are successfully included in the caller:

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

The results confirm that there is no difference in performance between invoking static and non-static methods.

 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 

However, the GC profiler indicates that the vararg Object[] array is allocated for loop* methods, but not for inline* methods.

 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 

I believe the starting point was that static methods are always monomorphic. However, the JVM can also embed polymorphic methods if there are not many real receivers on a particular call site.

+5
source

Source: https://habr.com/ru/post/1257359/


All Articles