Java反射,但速度更快
在编译时不知道Java类的最快方法是什么? Java框架通常会这样做。 很多。 它可以直接影响其性能。 因此,让我们对不同的方法进行基准测试,例如反射,方法句柄和代码生成。
用例
假设我们有一个简单的Person
类,其中包含名称和地址:
public class Person {...public String getName() {...}public Address getAddress() {...}}
并且我们想使用诸如以下的框架:
- XStream ,JAXB或Jackson来将实例序列化为XML或JSON。
- JPA /休眠将人员存储在数据库中。
- OptaPlanner分配地址(如果他们是游客或无家可归的人)。
这些框架都不了解Person
类。 因此,他们不能简单地调用person.getName()
:
// Framework codepublic Object executeGetter(Object object) {// Compilation error: class Person is unknown to the frameworkreturn ((Person) object).getName();}
相反,代码使用反射,方法句柄或代码生成。
但是这样的代码被称为很多 :
- 如果在数据库中插入1000个不同的人,则JPA / Hibernate可能会调用2000次这样的代码:
- 1000次调用
Person.getName()
- 1000次调用
- 同样,如果您用XML或JSON编写1000个不同的人,则XStream,JAXB或Jackson可能会进行2000次调用。
显然,当这种代码每秒被调用x次时, 其性能很重要 。
基准测试
使用JMH,我在带有32GB RAM的64位8核Intel i7-4790台式机上的Linux上使用OpenJDK 1.8.0_111运行了一组微型基准测试。 JMH基准测试有3个分支,5个1秒的预热迭代和1秒的20个测量迭代。
该基准的源代码位于此GitHub存储库中 。
TL; DR结果
- Java反射很慢。 (*)
- Java MethodHandles也很慢。 (*)
- 用
javax.tools
生成的代码很快。 (*)
(*)在用例中,我以使用的工作量作为基准。 你的旅费可能会改变。
因此,魔鬼在细节中。 让我们浏览一下实现,以确认我应用了典型的魔术技巧(例如setAccessible(true)
)。
实作
直接访问(基准)
我使用了一个普通的person.getName()
调用作为基准:
public final class MyAccessor {public Object executeGetter(Object object) {return ((Person) object).getName();}}
每次操作大约需要2.7纳秒:
Benchmark Mode Cnt Score Error Units
===================================================
DirectAccess avgt 60 2.667 ± 0.028 ns/op
直接访问自然是运行时最快的方法,而没有引导成本。 但是它在编译时导入Person
,因此每个框架都无法使用它。
反射
框架在运行时读取getter的明显方法是不预先知道它的方法是通过Java Reflection:
public final class MyAccessor {private final Method getterMethod;public MyAccessor() {getterMethod = Person.class.getMethod("getName");// Skip Java language access checking during executeGetter()getterMethod.setAccessible(true);}public Object executeGetter(Object bean) {return getterMethod.invoke(bean);}}
添加setAccessible(true)
调用可使这些反射调用更快,但是即使这样,每个调用也要花费5.5纳秒。
Benchmark Mode Cnt Score Error Units
===================================================
DirectAccess avgt 60 2.667 ± 0.028 ns/op
Reflection avgt 60 5.511 ± 0.081 ns/op
反射比直接访问慢106%(大约慢一倍)。 预热还需要更长的时间。
这对我来说不是什么大惊喜,因为当我使用OptaPlanner在980个城市中描述(使用抽样)一个人为简单的旅行商问题时,反射成本像拇指酸痛一样突出:
方法句柄
Java 7中引入了MethodHandle来支持invokedynamic指令。 根据javadoc,它是对基础方法的类型化,直接可执行的引用。 听起来很快,对不对?
public final class MyAccessor {private final MethodHandle getterMethodHandle;public MyAccessor() {MethodHandle temp = lookup.findVirtual(Person.class, "getName", MethodType.methodType(String.class));temp = temp.asType(temp.type().changeParameterType(0 , Object.class));getterMethodHandle = temp.asType(temp.type().changeReturnType(Object.class));}public Object executeGetter(Object bean) {return getterMethodHandle.invokeExact(bean);}}
不幸的是, MethodHandle甚至比 OpenJDK 8中的反射还要慢 。它每次操作花费6.1纳秒,因此比直接访问慢132%。
Benchmark Mode Cnt Score Error Units
===================================================
DirectAccess avgt 60 2.667 ± 0.028 ns/op
Reflection avgt 60 5.511 ± 0.081 ns/op
MethodHandle avgt 60 6.188 ± 0.059 ns/op
StaticMethodHandle avgt 60 5.481 ± 0.069 ns/op
话虽如此,如果MethodHandle在静态字段中,则每次操作只需要5.5纳秒,这仍然与反射一样慢 。 此外,对于大多数框架而言,这是无法使用的。 例如,JPA实现可能需要反映n
类( Person
, Company
, Order
等等)的m
getters( getName()
, getAddress()
, getBirthDate()
,...),因此JPA实现如何有n * m
静态字段,在编译时不知道n
或m
?
我确实希望MethodHandle在将来的Java版本中能够像直接访问一样快,从而取代对...的需求。
使用javax.tools.JavaCompiler生成的代码
在Java中,可以在运行时编译和运行生成的Java代码。 因此,使用javax.tools.JavaCompiler
API,我们可以在运行时生成直接访问代码:
public abstract class MyAccessor {public static MyAccessor generate() {final String String fullClassName = "x.y.generated.MyAccessorPerson$getName";final String source = "package x.y.generated;\n"+ "public final class MyAccessorPerson$getName extends MyAccessor {\n"+ " public Object executeGetter(Object bean) {\n"+ " return ((Person) object).getName();\n"+ " }\n"+ "}";JavaFileObject fileObject = new ...(fullClassName, source);JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();ClassLoader classLoader = ...;JavaFileManager javaFileManager = new ...(..., classLoader)CompilationTask task = compiler.getTask(..., javaFileManager, ..., singletonList(fileObject));boolean success = task.call();...Class compiledClass = classLoader.loadClass(fullClassName);return compiledClass.newInstance();}// Implemented by the generated subclasspublic abstract Object executeGetter(Object object);}
有关如何使用javax.tools.JavaCompiler
更多信息,请参见本文或本文的 第2页 。 除了javax.tools
之外,类似的方法也可以使用ASM或CGLIB,但是这些方法会推断出额外的依赖性,并且可能会产生不同的性能结果。
无论如何, 生成的代码与直接访问一样快 :
Benchmark Mode Cnt Score Error Units
===================================================
DirectAccess avgt 60 2.667 ± 0.028 ns/op
GeneratedCode avgt 60 2.745 ± 0.025 ns/op
因此,当我再次在OptaPlanner中运行该完全相同的Traveling Salesman问题时,这一次使用代码生成来访问计划变量, 因此总分计算速度提高了18% 。 并且分析(使用采样)看起来也更好:
请注意,在正常使用情况下,由于大量CPU需要实际复杂的分数计算,因此性能提升几乎是无法检测到的...
运行时代码生成的唯一缺点是,它会导致可观的引导成本,特别是如果生成的代码未进行批量编译时。 因此,我仍然希望有一天MethodHandles能够像直接访问一样快,只是为了避免增加引导成本。
结论
在此基准测试中,反射和MethodHandles的速度是OpenJDK 8中直接访问的两倍,但是生成的代码的速度是直接访问的速度。
Benchmark Mode Cnt Score Error Units
===================================================
DirectAccess avgt 60 2.667 ± 0.028 ns/op
Reflection avgt 60 5.511 ± 0.081 ns/op
MethodHandle avgt 60 6.188 ± 0.059 ns/op
StaticMethodHandle avgt 60 5.481 ± 0.069 ns/op
GeneratedCode avgt 60 2.745 ± 0.025 ns/op
翻译自: https://www.javacodegeeks.com/2018/01/java-reflection-much-faster.html
Java反射,但速度更快相关推荐
- java 反射 速度_Java反射,但速度更快
java 反射 速度 在编译时不知道Java类的最快方法是什么? Java框架通常会这样做. 很多. 它可以直接影响其性能. 因此,让我们对不同的方法进行基准测试,例如反射,方法句柄和代码生成. 用例 ...
- java反射机制+继承设计技巧
[0]README 0.1) 本文描述+源代码均 转自 core java volume 1, 旨在理解 java反射机制 :最后还顺带提出了 继承设计的技巧: [1]反射相关 1)反射定义:能够分析 ...
- Java 反射 (快速了解反射)
反射的概念 JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:这种动态获取信息以及动态调用对象方法的功能称为java ...
- java反射最佳实践,java反射性能测试分析
java反射性能测试分析 java有别于其他编程语言而让我着迷的特性有很多,其中最喜欢的是接口设计,他让我们设计的东西具有美感.同样反射也是我比较喜欢的一个特性,他让程序自动运行,动态加载成为了可能, ...
- java 获取 反射 方法 名_乐字节Java反射之一:反射概念与获取反射源头Class
一.Java反射机制概念 "程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言",如Python, Ruby是动态语言:显然C++,Java,C#不是动态语言,但是JAV ...
- Java反射以及应用
需求:需要通过反射动态获取类的字段类型,然后做特殊处理 Java反射getDeclaredField和getField的区别 getDeclaredFiled 只能获取类本身的属性成员(包括私有.共有 ...
- java反射用在哪里_Java反射
昨天去参加比赛了,所以没有进行博客迁移.人生中的第一场健体比赛,虽然没得奖,但是收获和带来的思考颇丰.意外地进入了男子B组(174以上)的半决赛,然后在半决赛的时候还被裁判员点名出去单独比较,这个很让 ...
- Java反射(详述版)
一.什么是反射? 我们先来看一个例子: package venus; public class Student {public String name;public Student(){System. ...
- java 反射 动态代理
在上一篇文章中介绍Java注解的时候,多次提到了Java的反射API.与javax.lang.model不同的是,通过反射API可以获取程序在运行时刻的内部结构.反射API中提供的动态代理也是非常强大 ...
最新文章
- 【廖雪峰python入门笔记】变量
- oracle类型不匹配,sys_refcursor的使用,报错类型不匹配
- python教程从入门到实践第八章_python:从入门到实践--第八章:函数
- GitHub上13个学习资源项目,值得收藏!
- Java:抽象类笔记
- oracle数据库中的系统自带表情_oracle 系统自带几个常用函数
- 【Kafka】KafkaConsumer is not safe for multi-threaded access
- 大型网络之---公司内部局域网
- 常用animation动画
- Python Web编程入门
- opencms Log研究
- winform自定义panel控件
- 详细教您如何把wav转换成mp3格式
- 三个技巧教你怎么裁剪视频画面,手残党也能掌握
- 实验吧-PHP大法-eregi()函数
- 微信群成员活跃度测试软件,微信群活跃度最佳人数是多少?
- 安卓控件button添加背景图片
- ​从机械工程师到机器学习工程师,我也是个数据科学家了
- Jquery颜色选择插件使用
- 基于C语言实现的自动打乱九宫格并且还原