java语言替代品
by Carlos Raphael
卡洛斯·拉斐尔(Carlos Raphael)
Java反射的更快替代品 (A faster alternative to Java Reflection)
In the article Specification Pattern, for the sake of sanity, I didn’t mention about an underlying component to nicely make that thing happen. Now, I’ll elaborate a little bit more around the JavaBeanUtil class, that I put in place to read the value for a given fieldName
from a particular javaBeanObject
, which in that occasion turned out to be FxTransaction.
在“ 规范模式”一文中 ,出于理智的考虑,我没有提到使该事情发生的基础组件。 现在,我将详细介绍JavaBeanUtil类,以便从特定的javaBeanObject
读取给定fieldName
的值,在这种情况下,该值原来是FxTransaction 。
You can easily argue I could’ve basically used Apache Commons BeanUtils or one of its alternatives to achieve the same result. But I was interested in getting my own hands dirty with something different that I knew would be way faster than any library built on top of the widely known Java Reflection.
您可以轻松地说出我本来可以使用Apache Commons BeanUtils或其替代方法之一来达到相同的结果。 但是我感兴趣的是用一种我知道的东西比在众所周知的Java Reflection之上构建的任何库更快的方式弄污自己。
The enabler of the technique used to avoid the very slow reflection is the invokedynamic
bytecode instruction. Briefly, invokedynamic
(or “indy”) was the greatest thing introduced in Java 7 in order to pave the way for implementing dynamic languages on top of the JVM through dynamic method invocation. It also later allowed lambda expression and method reference in Java 8 as well as string concatenation in Java 9 to benefit from it.
避免非常缓慢的反射的技术的推动者是invokedynamic
字节码指令。 简而言之, invokedynamic
(或“ indy”)是Java 7中引入的最重要的功能,它为通过动态方法调用在JVM之上实现动态语言铺平了道路。 后来,它还允许Java 8中的lambda表达式和方法引用以及Java 9中的字符串连接从中受益。
In a nutshell, the technique I’m about to better describe below leverages LambdaMetafactory and MethodHandle in order to dynamically create an implementation of Function. Its single method delegates a call to the actual target method with a code defined inside of lambda body.
简而言之,我将在下面更好地描述的技术利用LambdaMetafactory和MethodHandle来动态创建Function的实现。 它的单个方法使用在lambda主体内部定义的代码将调用委派给实际的目标方法。
The target method in question here is the actual getter method that has direct access to the field we want to read. Also, I should say if you are quite familiar with the nice things that came up within Java 8, you will find the below code snippets fairly easy to understand. Otherwise, it may be tricky at a glance.
这里讨论的目标方法是直接访问我们要读取的字段的实际getter方法。 另外,我应该说,如果您对Java 8中的出色功能非常熟悉,您将发现以下代码片段相当容易理解。 否则,乍看之下可能很棘手。
窥视自制JavaBeanUtil (A peek at the homemade JavaBeanUtil)
The following method is the utility used to read a value from a JavaBean field. It takes the JavaBean object and a single fieldA
or even nested field separated by periods, for example, nestedJavaBean.nestedJavaBean.fieldA
以下方法是用于从JavaBean字段读取值的实用程序。 它接受JavaBean对象和单个fieldA
或什至由句点分隔的嵌套字段,例如nestedJavaBean.nestedJavaBean.fieldA
For optimal performance, I’m caching the dynamically created function that is the actual way of reading the content of a given fieldName
. So inside the getCachedFunction
method, as you can see above, there’s a fast path leveraging the ClassValue for caching and there’s the slow createAndCacheFunction
path executed only if nothing has been cached so far.
为了获得最佳性能,我将缓存动态创建的函数,这是读取给定fieldName
内容的实际方法。 因此,如上所示,在getCachedFunction
方法内部,有一条利用ClassValue进行缓存的快速路径,并且只有在到目前为止尚未缓存任何内容的情况下,才执行慢速的createAndCacheFunction
路径。
The slow path will basically delegate to the createFunctions
method that is returning a list of functions to be reduced by chaining them using Function::andThen
. When functions are chained, you can imagine some sort of nested calls like getNestedJavaBean().getNestedJavaBean().getFieldA()
. Finally, after chaining we simply put the reduced function in the cache calling cacheAndGetFunction
method.
慢速路径基本上将委托给createFunctions
方法,该方法将通过使用Function::andThen
链接它们来减少要减少的Function::andThen
。 当函数链接在一起时,您可以想象像getNestedJavaBean().getNestedJavaBean().getFieldA()
这样的嵌套调用。 最后,在链接之后,我们只需将简化后的函数放在调用cacheAndGetFunction
方法的缓存中。
Drilling a bit more into the slow path of function creation, we need to individually navigate through the field path
variable by splitting it as per below:
深入研究函数创建的慢速路径,我们需要按如下所示拆分字段来分别浏览字段path
变量:
The above createFunctions
method delegates the individual fieldName
and its class holder type to createFunction
method, which will locate the needed getter based upon javaBeanClass.getDeclaredMethods()
. Once it’s located, it maps to a Tuple object (facility from Vavr library), that contains the return type of the getter method and the dynamically created function in which will act as if it was the actual getter method itself.
上面的createFunctions
方法将各个fieldName
及其类持有者类型委托给createFunction
方法,该方法将基于javaBeanClass.getDeclaredMethods()
定位所需的getter。 找到后,它映射到一个Tuple对象(来自Vavr库的工具),该对象包含getter方法的返回类型和动态创建的函数,该函数在其中的作用就好像它是实际的getter方法本身一样。
This tuple mapping is done by createTupleWithReturnTypeAndGetter
in conjunction with createCallSite
method as follows:
该元组映射由createTupleWithReturnTypeAndGetter
结合createCallSite
方法完成,如下所示:
In the above two methods, I make use of a constant called LOOKUP
, which is simply a reference to MethodHandles.Lookup. With that, I can create a direct method handle based on the previously located getter method. And finally, the created MethodHandle is passed to createCallSite
method whereby the lambda body for the function is produced using the LambdaMetafactory. From there, ultimately, we can obtain the CallSite instance, which is the function holder.
在以上两种方法中,我使用了一个名为LOOKUP
的常量,该常量只是对MethodHandles.Lookup的引用。 这样,我可以基于先前定位的getter方法创建直接方法句柄 。 最后,将创建的MethodHandle传递给createCallSite
方法,从而使用LambdaMetafactory生成函数的lambda主体。 最终,我们可以从那里获得CallSite实例,它是函数的所有者。
Note that if I wanted to deal with setters I could use a similar approach by leveraging BiFunction instead of Function.
请注意,如果我想处理二传手,则可以通过利用BiFunction而不是Function来使用类似的方法。
基准测试 (Benchmark)
In order to measure the gains of performance, I used the ever-awesome JMH (Java Microbenchmark Harness), which will likely be part of the JDK 12. As you may know, results are bound to the platform, so for reference I’ll be utilizing a single 1x6 i5-8600K 3.6GHz
and Linux x86_64
as well as Oracle JDK 8u191
and GraalVM EE 1.0.0-rc9
.
为了衡量性能的提高,我使用了令人敬畏的JMH( Java Microbenchmark Harness ),它很可能是JDK 12的一部分。 如您所知,结果与平台有关,因此作为参考,我将使用单个1x6 i5-8600K 3.6GHz
和Linux x86_64
以及Oracle JDK 8u191
和GraalVM EE 1.0.0-rc9
。
For comparison, I used Apache Commons BeanUtils, a well-known library for most Java developers, and one of its alternatives called Jodd BeanUtil which claims to be almost 20% faster.
为了进行比较,我使用了Apache Commons BeanUtils (这是大多数Java开发人员所熟知的库)及其替代方案之一,称为Jodd BeanUtil ,它声称快了近20% 。
Benchmark scenario is set as follows:
基准情景设置如下:
The benchmark is driven by how deep we are going to retrieve some value as per the four different levels specified above. For each fieldName
, JMH will perform 5 iterations of 3 seconds each to warm things up and then 5 iterations of 1 second each to actually measure. Each scenario will then repeat 3 times to reasonably gather the metrics.
基准是由我们根据上述四个不同级别检索某些值的深度决定的。 对于每个fieldName
,JMH将执行5次迭代(每次3秒)以预热事物,然后执行5次迭代(每秒钟1秒)以进行实际测量。 然后,每个方案将重复3次以合理收集指标。
结果 (Results)
Let’s start with the results gathered from the JDK 8u191
run:
让我们从JDK 8u191
运行收集的结果开始:
The worst scenario using invokedynamic
approach is much faster than the fastest scenario from the other two libraries. That’s a huge difference, and if you’re doubting the results, you can always download the source code and play around as you like.
使用invokedynamic
方法的最坏情况比其他两个库中最快的情况要快得多。 这是一个巨大的差异,如果您对结果有所怀疑,可以随时下载源代码并随意播放。
Now, let’s see how the same benchmark performs with GraalVM EE 1.0.0-rc9
现在,让我们看看相同的基准在GraalVM EE 1.0.0-rc9
Full results can be viewed here with the nice JMH Visualizer.
完整的结果可以在此处使用漂亮的JMH Visualizer查看。
观察结果 (Observations)
The huge difference is because JIT-compiler knows CallSite
and MethodHandle
very well and knows how to inline them quite well as opposed to the reflection approach. Also, you can see how promising GraalVM is. Its compiler does a truly awesome job being capable of a great performance enhancement for the reflection approach.
巨大的差异是因为JIT编译器非常了解CallSite
和MethodHandle
并且知道如何很好地内联它们,而不是反射方法。 此外,您可以看到GraalVM的前景如何。 它的编译器做的确实很棒,能够为反射方法带来极大的性能增强。
If you’re curious and want to play further, I encourage you to pull the source code from my Github. Bear in mind I’m not encouraging you to do your own homemade JavaBeanUtil
and use in production. Rather my aim here is to simply showcase my experiment and the possibilities we can get from invokedynamic
.
如果您好奇并想进一步学习,建议您从我的Github中提取源代码。 请记住,我不鼓励您做自己的自制JavaBeanUtil
并在生产中使用。 相反,我的目的只是简单地展示我的实验以及我们从invokedynamic
可以得到的可能性。
翻译自: https://www.freecodecamp.org/news/a-faster-alternative-to-java-reflection-db6b1e48c33e/
java语言替代品