是否可以使java.lang.invoke.MethodHandle与直接调用一样快?
我正在比较是否可以使java.lang.invoke.MethodHandle与直接调用一样快?,java,performance,jvm,jit,jmh,Java,Performance,Jvm,Jit,Jmh,我正在比较MethodHandle::invoke和直接静态方法调用的性能。以下是静态方法: 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;
MethodHandle::invoke
和直接静态方法调用的性能。以下是静态方法:
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
的性能有所下降
使用-prof perfasm
运行它会显示以下内容:
....[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)
据我所知,基准测试结果的原因是最热区域2org.sample.generated.MyBenchmark\u finalMethodHandle\u jmhTest::finalMethodHandle\u avgt\u jmhStub
包含JHM循环中MethodHandle::invoke
执行的所有类型检查。程序集输出片段(某些代码未写入):
在调用invokeBasic
之前,我们执行影响输出avgt的类型检查(在jmh循环内)
问题:为什么没有将所有类型检查移到循环之外?我宣布
公共最终方法处理mhh代码>在基准测试中。所以我希望编译器能够解决这个问题并消除相同的类型检查。如何消除相同的类型检查?有可能吗?使方法处理mhh
静态:
Benchmark Mode Samples Score Error Units
directMethodCall avgt 5 0,942 ± 0,095 ns/op
finalMethodHandle avgt 5 0,906 ± 0,078 ns/op
非静态:
Benchmark Mode Samples Score Error Units
directMethodCall avgt 5 0,897 ± 0,059 ns/op
finalMethodHandle avgt 5 4,041 ± 0,463 ns/op
您可以使用MethodHandle
的反射调用。它的工作原理大致类似于Method.invoke
,但运行时检查较少,并且没有装箱/取消装箱。由于这个MethodHandle
不是static final
,JVM不会将其视为常量,也就是说,MethodHandle的目标是一个黑盒,不能内联
尽管mhh
是final,但它包含在每次迭代中重新加载的实例字段,如MethodType
和LambdaForm表单
。由于内部有一个黑匣子调用(见上文),这些负载没有吊出回路。此外,MethodHandle
的LambdaForm
可以在调用之间的运行时进行更改(自定义),因此需要重新加载
如何使通话更快?
使用static final
MethodHandle。JIT将知道这种MethodHandle的目标,因此可以在调用站点内联它
即使有非静态MethodHandle,也可以将其绑定到静态调用站点,并以与直接方法一样快的速度调用它。这类似于lambda的调用方式
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);
}
使用常规的invokeinterface
而不是@Holger建议的MethodHandle.invoke
。可以使用生成用于调用给定MethodHandle的接口实例
很酷,真的很管用。现在,它调用的MethodHandle::invoke
和实际的IntSum::sum
只是内联到jmh循环中。为什么?怎么搞的?在非静态的情况下可以这样做吗?@St.Antario我同意,为什么添加静态会起作用我认为这只是这里设置的一个问题,所以我编写了我自己的版本(使用一个设置类),但结果与您的情况相同,相差两倍……该方法具有signatureMethodHandle.invoke(Object…args)
。int
值是否也可能被自动装箱/取消装箱?看起来这个类中有很多黑魔法。@flakes这是签名多态方法,由javac
进行特殊处理。您可以查看编译后的字节码。编译方法的签名是MethodHandle.invoke(II)I
Ah,这对我来说是一个新概念。狂野@顺便说一句,@PolymorphicSignature
不是公共的。我们不能自己创建这样的方法:)。这取决于JRE版本;有一些实现使用的是invoke
要比invokeExact
慢得多,因此如果您有选择,请选择invokeExact
。如果它在Java版本中没有帮助,那么也不会有什么坏处。顺便问一下,你做了多少次热身?根据我的经验,方法句柄需要大量的预热…请原谅我的无知,但是如果OP知道调用站点不会更改,那么该代码是否可以改为使用ConstantCallSite
?如果是,由于它是一个常量CallSite
,那么它也需要是静态的吗?@EugeneConstantCallSite
需要在构造函数中指定目标方法。从这个意义上讲,ConstantCallSite
是无用的-这与直接创建静态MethodHandle
相同<另一方面,code>MutableCallSite
允许将有关目标的决策延迟到运行时的稍后时间。啊。。。这意味着常量折叠仅应用于静态final
。出于某种原因,我认为如果我们将一个不可变字段声明为final
,编译器可以知道它是不可变的和最终的,并在循环之外执行一些绑定检查(在我的例子中)。也许你知道在哪里可以找到JIT提升/持续折叠?我查看了这个包,但它看起来很模糊。…@St.Antario Final非静态字段默认情况下不被视为常量,除非设置了-XX:+TrustFinalNonStaticFields
。请参阅。@Eugene顺便说一句,SignaturePolymorphic
方法在JVM中有严格的定义。因此,除了java.lang.invoke
中的方法外,没有其他方法可以使用。
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);
}