是否有可能使java.lang.invoke.MethodHandle与直接调用一样快?

St.*_*rio 8 java performance jit jvm jmh

我正在比较性能MethodHandle::invoke和直接静态方法调用.这是静态方法:

public class IntSum {
    public static int sum(int a, int b){
        return a + b;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的基准:

@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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我得到了以下结果:

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
Run Code Online (Sandbox Code Playgroud)

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) 
Run Code Online (Sandbox Code Playgroud)

据我所知,基准测试结果的原因是Hottest Region 2 org.sample.generated.MyBenchmark_finalMethodHandle_jmhTest::finalMethodHandle_avgt_jmhStub包含MethodHandle::invoke了JHM循环内部执行的所有类型检查.汇编输出片段(省略一些代码):

....[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 
;...
Run Code Online (Sandbox Code Playgroud)

在调用之前,invokeBasic我们执行类型检查(在jmh循环内),这会影响输出avgt.

问题:为什么不是所有的类型检查都移出循环之外?我public final MethodHandle mhh;在基准内宣布.所以我希望编译器可以解决它并消除相同的类型检查.如何消除相同的类型检查?可能吗?

apa*_*gin 9

你使用反射调用MethodHandle.它大致类似Method.invoke,但运行时检查较少,没有装箱/拆箱.由于MethodHandle不是这样static final,JVM不会将其视为常量,也就是说,MethodHandle的目标是一个黑盒子,不能内联.

即使mhh是最终的,它包含了像实例字段MethodType typeLambdaForm form那些在每次迭代重新加载.由于内部有黑框调用,因此这些负载不会从循环中提升(参见上文).此外,LambdaForma MethodHandle可以在调用之间的运行时更改(自定义),因此需要重新加载.

如何更快地打电话?

  1. 使用static finalMethodHandle.JIT将知道此类MethodHandle的目标,因此可以在呼叫站点内联它.

  2. 即使你有非静态的MethodHandle,你也可以将它绑定到静态CallSite,并像直接方法一样快速调用它.这类似于lambdas的调用方式.

    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);
    }
    
    Run Code Online (Sandbox Code Playgroud)
    1. 使用常规invokeinterface而不是MethodHandle.invoke@Holger建议.可以使用生成调用给定MethodHandle的接口实例LambdaMetafactory.metafactory().

  • @ St.Antario默认情况下,最终的非静态字段不被视为常量,除非设置了`-XX:+ TrustFinalNonStaticFields`.参见[`ciField :: initialize_from`](http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/de8045923ad2/src/share/vm/ci/ciField.cpp#l201). (2认同)

Nik*_*lay 5

使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
Run Code Online (Sandbox Code Playgroud)

非静态:

Benchmark            Mode  Samples  Score   Error  Units
directMethodCall     avgt        5  0,897 ± 0,059  ns/op
finalMethodHandle    avgt        5  4,041 ± 0,463  ns/op
Run Code Online (Sandbox Code Playgroud)