TL; DR

请提供用某种著名的动态语言(例如JavaScript)编写的代码,以及使用invokedynamic在Java字节码中显示的代码,并解释为什么在这里使用invokedynamic是一个进步。

背景

我已经在Google上搜索了很多书,并且阅读了很多关于“不再是新的”调用动态指令的信息,互联网上的每个人都同意这将有助于加快JVM上动态语言的速度。 Thanks to stackoverflow我设法使自己的字节码指令与Sable / Jasmin一起运行。

我已经知道invokedynamic对于惰性常量很有用,而且我还认为the OpenJDK takes advantage of invokedynamic for lambdas是如何理解的。

Oracle具有a small example,但是据我所知,这种情况下对invokedynamic的使用无法达到目的,因为“adder”的示例可以更简单,更快,并且具有与以下字节码大致相同的效果:

aload whereeverAIs
checkcast java/lang/Integer
aload whereeverBIs
checkcast java/lang/Integer
invokestatic IntegerOps/adder(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;

因为某种原因,Oracle的bootstrap方法知道两个参数都是整数。他们甚至“承认”:

[..]假设参数[..]将是Integer对象。如果引导程序方法的参数(在此示例中为callerClass,dynMethodName和dynMethodType)变化,则引导程序方法需要其他代码才能正确链接invokedynamic [..]。

好吧,是的,没有有趣的“附加代码”,在这里使用invokedynamic是没有意义的,是吗?

因此,在那之后,再加上另外两个Javadoc和Blog条目,我认为我对如何使用invokedynamic进行很好的掌握,当invokestatic / invokevirtual / invokevirtual或getfield可以正常工作时,如何使用invokedynamic作为不良替代品。

现在,我很好奇如何实际将invokedynamic指令应用于现实世界的用例,从而实际上比“传统”调用(除了惰性常量,我得到了这些)有一些改进。

最佳答案

实际上,如果您广泛地使用术语“惰性创建”,则惰性操作是invokedynamic的主要优点。例如,Java 8的lambda创建功能是一种惰性创建,其中包括以下可能性:包含最终将由invokedynamic指令调用的代码的实际类甚至在该指令执行之前都不存在。

可以将其投影到各种脚本语言中,以与Java字节码不同的形式交付代码(甚至在源代码中也可以)。在这里,代码可以在第一次调用方法之前进行编译,并在之后保持链接状态。但是,如果脚本语言支持方法的重新定义,它甚至可能变得未链接。这使用了invokedynamic的第二个重要功能,允许可变的CallSite s,之后可以更改,同时在频繁调用而无需重新定义时支持最大性能。

此后可以更改invokedynamic目标的可能性允许使用另一个选项,即在第一次调用时链接到已解释的执行,计算执行次数并仅在超过阈值后才编译代码(然后再链接到已编译的代码)。

关于基于运行时实例的动态方法分派(dispatch),很明显invokedynamic不能忽略分派(dispatch)算法。但是,如果您在运行时检测到特定的调用站点将始终调用相同具体类型的方法,则可以将CallSite重新链接到优化代码,这将对目标是否为预期类型并执行优化操作进行简短检查,然后但是只有在测试失败的情况下,才会分支到执行完全动态分配的通用代码。如果实现检测到快速路径检查失败一定次数,则该实现甚至可以取消对此类 call 站点的优化。

这与在JVM中内部优化invokevirtualinvokeinterface的方式非常接近,因为大多数情况下,这些指令都是在相同的具体类型上调用的。因此,使用invokedynamic,您可以对任意查找算法使用相同的技术。

但是,如果您想要一个完全不同的用例,则可以使用invokedynamic来实现标准访问修饰符规则不支持的friend语义。假设您有一个AB类,它们具有这样的friend关系,即允许A调用privateB方法。然后,所有这些调用都可以被编码为具有所需名称和签名的invokedynamic指令,并指向public中的B引导程序方法,如下所示:

public static CallSite bootStrap(Lookup l, String name, MethodType type)
    throws NoSuchMethodException, IllegalAccessException {
    if(l.lookupClass()!=A.class || (l.lookupModes()&0xf)!=0xf)
      throw new SecurityException("unprivileged caller");
    l=MethodHandles.lookup();
    return new ConstantCallSite(l.findStatic(B.class, name, type));
}

它首先验证所提供的Lookup对象具有对A的完全访问权限,因为只有A能够构造此类对象。因此,在这个地方可以解决错误 call 者的偷偷摸摸的企图。然后,它使用对Lookup具有完全访问权限的B对象来完成链接。因此,在第一次调用之后,这些invokedynamic指令中的每一个都永久链接到private的匹配B方法,其运行速度与之后的普通调用相同。

09-27 09:04