是否可以使java.lang.invoke.MethodHandle像直接调用一样快? [英] Is it possible to make java.lang.invoke.MethodHandle as fast as direct invokation?
问题描述
我正在比较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
包含了
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像直接调用一样快?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!