问题描述
我正在比较MethodHandle::invoke和直接静态方法调用的性能.这是静态方法:
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; } }
这是我的基准:
@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; } }
我得到以下结果:
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具有一些性能下降.
MethodHandle has some performance degradation.
使用-prof perfasm运行它会显示以下内容:
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)
据我能确定基准测试结果的原因是 最热区域2 org.sample.generated.MyBenchmark_finalMethodHandle_jmhTest::finalMethodHandle_avgt_jmhStub包含了在JHM循环中.程序集输出片段(省略了一些代码):
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 ;...
在调用invokeBasic之前,我们执行类型检查(在jmh循环内部),这会影响输出avgt.
Before calling the invokeBasic we perform the type-checking (inside the jmh loop) which affects the output avgt.
问题: :为什么所有类型检查都没有移到循环之外?我在基准测试中声明了public final MethodHandle mhh;.因此,我希望编译器可以解决该问题并消除相同的类型检查.如何消除相同的类型检查?有可能吗?
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.它的工作原理与Method.invoke类似,但是运行时检查较少,并且没有装箱/拆箱.由于此MethodHandle不是static final,因此JVM不会将其视为常量,也就是说,MethodHandle的目标是黑匣子,无法内联.
You use reflective invocation of MethodHandle. It works roughly like Method.invoke, but with less run-time checks and without boxing/unboxing. Since this MethodHandle is not static final, JVM does not treat it as constant, that is, MethodHandle's target is a black box and cannot be inlined.
即使mhh是最终版本,它也包含诸如MethodType type和LambdaForm form之类的实例字段,这些实例字段在每次迭代时都会重新加载.由于内部有黑匣子调用,因此无法将这些负载提升到循环之外(请参见上文).此外,MethodHandle的LambdaForm可以在运行时在两次调用之间进行更改(自定义),因此需要进行重新加载.
Even though mhh is final, it contains instance fields like MethodType type and LambdaForm form that are reloaded on each iteration. These loads are not hoisted out of the loop because of a black-box call inside (see above). Furthermore, LambdaForm of a MethodHandle can be changed (customized) in run-time between calls, so it needs to be reloaded.
-
使用static final MethodHandle. JIT将知道此类MethodHandle的目标,因此可以在调用站点内联它.
Use static final MethodHandle. JIT will know the target of such MethodHandle and thus may inline it at the call site.
即使您具有非静态的MethodHandle,也可以将其绑定到静态的CallSite并像直接方法一样快速地调用它.这类似于lambda的调用方式.
Even if you have non-static MethodHandle, you may bind it to a static CallSite and invoke it as fast as direct methods. This is similar to how lambdas are called.
private static final MutableCallSite callSite = new MutableCallSite( MethodType.methodType(int.class, int.class, int.class)); private static final MethodHandle invoker = callSite.dynamicInvoker(); public MethodHandle mh; public MyBenchmark() { mh = ...; callSite.setTarget(mh); } @Benchmark public int boundMethodHandle() throws Throwable { return (int) invoker.invokeExact(first, second); }
- 按照@Holger的建议,使用常规的invokeinterface代替MethodHandle.invoke.可以使用 LambdaMetafactory.metafactory() .
这篇关于是否可以使java.lang.invoke.MethodHandle像直接调用一样快?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!