是否可以使 java.lang.invoke.MethodHandle 与直接调用一样快?
Is it possible to make java.lang.invoke.MethodHandle as fast as direct invokation?
我正在比较 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)
据我所知,基准测试结果的原因是 Hottest Region 2 org.sample.generated.MyBenchmark_finalMethodHandle_jmhTest::finalMethodHandle_avgt_jmhStub
包含JHM 循环中 MethodHandle::invoke
执行的所有类型检查。汇编输出片段(省略了一些代码):
....[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
之前,我们执行影响输出 avgt 的类型检查(在 jmh 循环内)。
问题: 为什么没有将所有类型检查都移出循环?我在基准测试中声明了 public final MethodHandle mhh;
。所以我希望编译器能够弄清楚并消除相同的类型检查。如何消除相同的类型检查?可能吗?
使MethodHandle 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
Non-static:
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
大致相同,但检查较少 run-time 并且没有 boxing/unboxing。由于这个MethodHandle
不是static final
,所以JVM不会把它当作常量,即MethodHandle的target是一个黑盒,不能内联
尽管 mhh
是最终的,但它包含在每次迭代时重新加载的实例字段,例如 MethodType type
和 LambdaForm form
。由于内部的 black-box 调用(见上文),这些负载不会被提升到循环之外。此外,MethodHandle
的 LambdaForm
可以在调用之间 run-time 中更改(自定义),因此 需要 重新加载。
如何让通话更快?
使用 static final
方法句柄。 JIT 将知道此类 MethodHandle 的目标,因此可以在调用站点将其内联。
即使您有 non-static MethodHandle,您也可以将它绑定到静态 CallSite 并像直接方法一样快速调用它。这类似于 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
。可以使用 LambdaMetafactory.metafactory()
. 生成用于调用给定 MethodHandle 的接口实例
我正在比较 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)
据我所知,基准测试结果的原因是 Hottest Region 2 org.sample.generated.MyBenchmark_finalMethodHandle_jmhTest::finalMethodHandle_avgt_jmhStub
包含JHM 循环中 MethodHandle::invoke
执行的所有类型检查。汇编输出片段(省略了一些代码):
....[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
之前,我们执行影响输出 avgt 的类型检查(在 jmh 循环内)。
问题: 为什么没有将所有类型检查都移出循环?我在基准测试中声明了 public final MethodHandle mhh;
。所以我希望编译器能够弄清楚并消除相同的类型检查。如何消除相同的类型检查?可能吗?
使MethodHandle 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
Non-static:
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
大致相同,但检查较少 run-time 并且没有 boxing/unboxing。由于这个MethodHandle
不是static final
,所以JVM不会把它当作常量,即MethodHandle的target是一个黑盒,不能内联
尽管 mhh
是最终的,但它包含在每次迭代时重新加载的实例字段,例如 MethodType type
和 LambdaForm form
。由于内部的 black-box 调用(见上文),这些负载不会被提升到循环之外。此外,MethodHandle
的 LambdaForm
可以在调用之间 run-time 中更改(自定义),因此 需要 重新加载。
如何让通话更快?
使用
static final
方法句柄。 JIT 将知道此类 MethodHandle 的目标,因此可以在调用站点将其内联。即使您有 non-static MethodHandle,您也可以将它绑定到静态 CallSite 并像直接方法一样快速调用它。这类似于 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
。可以使用LambdaMetafactory.metafactory()
. 生成用于调用给定 MethodHandle 的接口实例
- 使用常规