java语言替代品_Java反射的更快替代品

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.

简而言之,我将在下面更好地描述的技术利用LambdaMetafactoryMethodHandle来动态创建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 8u191GraalVM 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编译器非常了解CallSiteMethodHandle并且知道如何很好地内联它们,而不是反射方法。 此外,您可以看到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语言替代品

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值