Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
是否可以使java.lang.invoke.MethodHandle与直接调用一样快?_Java_Performance_Jvm_Jit_Jmh - Fatal编程技术网

是否可以使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) 
据我所知,基准测试结果的原因是最热区域2
org.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我同意,为什么添加静态会起作用我认为这只是这里设置的一个问题,所以我编写了我自己的版本(使用一个设置类),但结果与您的情况相同,相差两倍……该方法具有signature
    MethodHandle.invoke(Object…args)
    int
    值是否也可能被自动装箱/取消装箱?看起来这个类中有很多黑魔法。@flakes这是签名多态方法,由
    javac
    进行特殊处理。您可以查看编译后的字节码。编译方法的签名是
    MethodHandle.invoke(II)I
    Ah,这对我来说是一个新概念。狂野@顺便说一句,
    @PolymorphicSignature
    不是公共的。我们不能自己创建这样的方法:)。这取决于JRE版本;有一些实现使用的是
    invoke
    要比
    invokeExact
    慢得多,因此如果您有选择,请选择
    invokeExact
    。如果它在Java版本中没有帮助,那么也不会有什么坏处。顺便问一下,你做了多少次热身?根据我的经验,方法句柄需要大量的预热…请原谅我的无知,但是如果OP知道调用站点不会更改,那么该代码是否可以改为使用
    ConstantCallSite
    ?如果是,由于它是一个常量
    CallSite
    ,那么它也需要是静态的吗?@Eugene
    ConstantCallSite
    需要在构造函数中指定目标方法。从这个意义上讲,
    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);
    }