I'm comparing performance of MethodHandle::invoke
and direct static method invokation. Here is the static method:
public class IntSum {
public static int sum(int a, int b){
return a + b;
}
}
And here is my benchmark:
@State(Scope.Benchmark)
public class MyBenchmark {
public int first;
public int second;
public final MethodHandle mhh;
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public int directMethodCall() {
return IntSum.sum(first, second);
}
@Benchmark
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public int finalMethodHandle() throws Throwable {
return (int) mhh.invoke(first, second);
}
public MyBenchmark() {
MethodHandle mhhh = null;
try {
mhhh = MethodHandles.lookup().findStatic(IntSum.class, "sum", MethodType.methodType(int.class, int.class, int.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
e.printStackTrace();
}
mhh = mhhh;
}
@Setup
public void setup() throws Exception {
first = 9857893;
second = 893274;
}
}
I got the following result:
Benchmark Mode Cnt Score Error Units
MyBenchmark.directMethodCall avgt 5 3.069 ± 0.077 ns/op
MyBenchmark.finalMethodHandle avgt 5 6.234 ± 0.150 ns/op
MethodHandle
has some performance degradation.
Running it with -prof perfasm
shows this:
....[Hottest Regions]...............................................................................
31.21% 31.98% C2, level 4 java.lang.invoke.LambdaForm$DMH::invokeStatic_II_I, version 490 (27 bytes)
26.57% 28.02% C2, level 4 org.sample.generated.MyBenchmark_finalMethodHandle_jmhTest::finalMethodHandle_avgt_jmhStub, version 514 (84 bytes)
20.98% 28.15% C2, level 4 org.openjdk.jmh.infra.Blackhole::consume, version 497 (44 bytes)
As far as I could figure out the reason for the benchmark result is that the Hottest Region 2 org.sample.generated.MyBenchmark_finalMethodHandle_jmhTest::finalMethodHandle_avgt_jmhStub
contains all the type-checks performed by the MethodHandle::invoke
inside the JHM loop. Assembly output fragment (some code ommitted):
....[Hottest Region 2]..............................................................................
C2, level 4, org.sample.generated.MyBenchmark_finalMethodHandle_jmhTest::finalMethodHandle_avgt_jmhStub, version 519 (84 bytes)
;...
0x00007fa2112119b0: mov 0x60(%rsp),%r10
;...
0x00007fa2112119d4: mov 0x14(%r12,%r11,8),%r8d ;*getfield form
0x00007fa2112119d9: mov 0x1c(%r12,%r8,8),%r10d ;*getfield customized
0x00007fa2112119de: test %r10d,%r10d
0x00007fa2112119e1: je 0x7fa211211a65 ;*ifnonnull
0x00007fa2112119e7: lea (%r12,%r11,8),%rsi
0x00007fa2112119eb: callq 0x7fa211046020 ;*invokevirtual invokeBasic
;...
0x00007fa211211a01: movzbl 0x94(%r10),%r10d ;*getfield isDone
;...
0x00007fa211211a13: test %r10d,%r10d
;jumping at the begging of jmh loop if not done
0x00007fa211211a16: je 0x7fa2112119b0 ;*aload_1
;...
Before calling the invokeBasic
we perform the type-checking (inside the jmh loop) which affects the output avgt.
QUESTION: Why isn't all the type-check moved outside of the loop? I declared public final MethodHandle mhh;
inside the benchmark. So I expected the compiler can figured it out and eliminate the same type-checks. How to make the same typechecks eliminated? Is it possible?
MethodHandle.invoke(Object... args)
. Is it possible that theint
values are also being auto-boxed/unboxed? Looks like there's a lot of black magic in this class. – Valerivaleriajavac
. You can look at the compiled bytecode. The signature of the compiled method isMethodHandle.invoke(II)I
– Armpit@PolymorphicSignature
is not public. We cannot create methods like this by ourselves :). – ArmpitinvokeExact
? And which Java version did you use? When using Java 8 and having an interface with a matching signature, you can convert direct method handles to interface implementations viaLambdaMetaFactory
, as shown in this answer. – CounterblowinvokeExact
but the problem was that I did not get any performance improvement. Compiled code was also the same (the same type checks). Anyway,invoke
works the same asinvokeExact
if theMethodType
matches, doesn't it? – Armpitinvoke
was significantly slower thaninvokeExact
, so if you have a choice, preferinvokeExact
. If it doesn’t help in your Java version, it doesn’t hurt either. By the way, how much warmup iterations did you have? To my experience, method handles need a lot of warmup… – Counterblow@PolymorphicSignature
- compiler overloads... :) of course we are not suppose to get a handle of those. btw@ForceInline
is private also, but JMH somehow has@CompilerControl(CompilerControl.Mode.INLINE)
(even if stated that this could be ignored) – BergeracLambdaMetaFactory
for direct handles, whenever possible. – Counterblow