logo头像
Snippet 博客主题

反射性能比较

背景

最近做央行征信项目在最初的一版设计尝试使用反射来进行方法调用,于是又”深入”的了解下反射的性能。
一开始看到网上的文章一篇看起来比较专业的性能对比测试,测试结果如下:
网上反射性能对比
从图中可以看到,在最后一行ReflectAsm可谓是性能出众,一骑绝尘让其他反射方式难以望其项背。
周末有时间刚好想自己也对反射进行一个性能比较,顺便看看ReflectAsm如此出众是如何做到的,于是噩梦开始。

第一步 写个像模像样的测试代码

实验机器:
MacBook Pro (Retina, 13-inch, Early 2015)
处理器 2.9 GHz Intel Core i5
内存 8 GB 1867 MHz DDR3
实验环境:
java version “1.8.0_101”
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)
ReflectAsm版本:1.11.9

本实验不重复实例化对象,只做方法调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class ReflectTest {

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, InterruptedException {
long count = 1000000000;

CalculateTest calculateTest = new CalculateTest();
Class<?> c = Class.forName("wiki.hitime.demo.service.CalculateTest");
Class<?>[] params = new Class[1];
params[0] = Integer.class;
Method setNumMethod = c.getMethod("setNum", params);
MethodAccess methodAccess = MethodAccess.get(c);
int index = methodAccess.getIndex("setNum");

Profiler profiler = new Profiler("Reflection count=" + count);

profiler.start("直接调用");
for (int i = 0; i < count; ++i) {
calculateTest.setNum(i);
}

profiler.start("标准反射");
for (int i = 0; i < count; ++i) {
setNumMethod.invoke(calculateTest, i);
}

profiler.start("ReflectAsm反射");
for (int i = 0; i < count; ++i) {
methodAccess.invoke(calculateTest, index, i);
}

profiler.start("ReflectionUtils反射");
for (int i = 0; i < count; ++i) {
ReflectionUtils.invokeMethod(setNumMethod, calculateTest, i);
}

profiler.stop();
profiler.print();
}
}

第二步 拿到测试结果大肆夸赞ReflectAsm

这一步很重要,一定要拿到一个差距悬殊的结果,为后期夸赞ReflectAsm做好铺垫。原本计划是”运行”一点,图片到手,结论就有。哪知结果跌掉眼镜,如图:
跌掉眼镜的结果
总体来看ReflectAsm确实性能出色,执行10亿次耗时3.759秒值得表扬,可是反射什么时候比直接调用还要快,难道在我埋头业务这几年”大清亡了”?ReflectAsm快我们假设是后起之秀,但是这些反射没有一个比直接调用慢的,这是为何?
在我思考良久排除了代码问题,得出一个结论”先出场的死得早”,于是我调换了执行顺序,如下图:
ReflectAsm先出场
标准反射先出场
从上两图可以看出,确实是先出场的都是配角,总体来看先执行的循环因为”宇宙射线”、”太阳黑子”和”黑洞引力”等不可抗拒原因跑的非常慢,至于为何会慢需要后面再来调查,毕竟本文是做反射性能对比的。
于是我为了保证基础环境一致进行了分次运行的方式来进行性能统计,结果如下图:
直接调用
标准反射
ReflectAsm
ReflectionUtils
从总体结果来看,耗时为:标准反射>ReflectionUtils>ReflectAsm>直接调用,但是这样的结果已经并不重要了,总体大家的性能相差无几并没有网上科普文讲的差距悬殊。

第三步 结论

从整体实验过程来看,实验手法有时候决定结果的好坏,如果一开始我将直接调用放在最后,我可能已经得到一个看起来”公正”的结论。
从这次实验结论来看,做对比的过程才是实验精髓,结果反倒不那么重要了。根据过程我总结了几点结论:

  1. 这种顺序执行对比的方式可能并不适合性能类的实验
  2. ReflectAsm和JDK带的反射性能相差不大,而就只方法调用来看反射和直接调用性能差距也不大(怀疑网上是JDK1.7,而我用的1.8的原因)
  3. 强烈建议还是少用反射(本次拿出来的对比结果只是方法调用,实际在这之前我还做过包含对象实例化的对比,结果惨不忍睹)
微信打赏

赞赏是不耍流氓的鼓励