比Java反射更快的替代方案插图

invokedynamic

字节码指令是用来避免非常慢的反射的技术的实现者。简而言之,invokedynamic(或“indy”)是java7中引入的最伟大的东西,目的是为通过动态方法调用在JVM之上实现动态语言铺平道路。后来它还允许java8中的lambda表达式和方法引用以及java9中的字符串连接从中受益。

简而言之,下面我将利用lambdametfactoryMethodHandle来动态创建函数的实现。它的单个方法使用lambda body内部定义的代码委托对实际目标方法的调用。

这里讨论的目标方法是实际的getter方法,它可以直接访问我们要读取的字段。另外,我应该说,如果您非常熟悉Java8中出现的好东西,您会发现下面的代码片段非常容易理解。否则,乍一看可能会很棘手。

看看自制的JavaBean

下面的方法是用于从JavaBean字段读取值的实用程序。它使用JavaBean对象和一个单独的fieldA,甚至是用句点分隔的嵌套字段,例如:nestedJavaBean.nestedJavaBean.fieldA

为了获得最佳性能,我缓存了动态创建的函数,这是读取给定字段名内容的实际方式。因此,在getCachedFunction方法内部,正如您在上面看到的,有一个利用类值进行缓存的快速路径,还有一个缓慢的createAndCacheFunction路径,只有在到目前为止没有缓存的情况下才会执行。

慢路径基本上将委托给createFunctions方法,该方法通过使用Function::and then链接返回要缩减的函数列表。当getFieldBean()调用嵌套的JavaBean()时。最后,在链接之后,我们只需将简化后的函数放入缓存中调用cacheAndGetFunction方法。

在函数创建的慢路径上再深入一点,我们需要通过拆分field path变量来单独导航,如下所示:

上面的createFunctions方法将单个字段名及其类持有者类型委托给createFunction方法,该方法将根据javaBeanClass.getDeclaredMethods(). 一旦找到它,它就映射到一个元组对象,它包含getter方法的返回类型和动态创建的函数,在这个函数中它将充当实际的getter方法本身。

此元组映射由createTupleWithReturnTypeAndGettercreateCallSite方法一起完成,如下所示:

在以上两个方法中,我使用了一个名为LOOKUP的常量,它只是对MethodHandles.查找. 这样,我就可以基于先前找到的getter方法创建一个直接方法句柄。最后,将创建的MethodHandle传递给createCallSite方法,从而使用lambdametfactory生成函数的lambda主体。从那里,我们最终可以获得CallSite实例,它是函数持有者。

注意,如果我想处理setter,我可以使用类似的方法来利用BiFunction而不是Function

基准测试

为了衡量性能的提高,我使用了非常棒的JMH(java microbenchmark-Harness),它很可能是jdk12的一部分。如您所知,结果是绑定到平台上的,因此作为参考,我将使用单个1×6 i5-8600K 3.6GHz和Linux x86_64,以及Oracle JDK 8u191和GraalVM EE 1.0.0-rc9。

作为比较,我使用了ApacheCommonsBeanutils,一个为大多数Java开发人员所熟知的库,以及它的一个叫做JoddBeanutil的替代品,它声称速度快了20%。

基准场景设置如下:

基准测试取决于我们将根据上面指定的四个不同级别检索某些值的深度。对于每个字段名,JMH将执行5次迭代,每次3秒来预热,然后进行5次迭代,每次迭代时间为1秒,以便实际测量。然后,每个场景将重复3次,以合理地收集度量。

测试结果

让我们从JDK 8u191运行中收集的结果开始:

比Java反射更快的替代方案插图1

使用invokedynamic方法的最坏场景比其他两个库中最快的场景快得多。这是一个巨大的区别,如果你对结果有疑问,你可以随时下载源代码,随意玩玩。

源码地址:http://github.com/carlosraphael/javabeanutil-benchmark

现在,让我们看看相同的基准测试在Graalvmee1.0.0-rc9中的表现

比Java反射更快的替代方案插图2

关于反射的其他替代方案可以还可以参考这篇文章:https://javakk.com/762.html

总结

最大的区别是因为JIT编译器非常了解CallSiteMethodHandle,并且知道如何将它们内联起来,而不是反射方法。另外,你可以看到GraalVM是多么有前途。它的编译器做了一个真正了不起的工作,能够极大地提高反射方法的性能。

如果你很好奇,想继续玩下去,我建议你从我的Github中获取源代码。请记住,我并不鼓励您自己制作JavaBeanUtil并在生产中使用。我的目的是简单地展示我的实验和invokedynamic的可能性。