把成员变量转换成局部变量会更快吗?
直接看一段测试
测试结果
可以看到两者几乎没有差别。这是为什么呢?我们都知道,局部变量可以在栈上直接获取,有直接的字节码指令,而成员变量则需要取到对象,再取到字段值,要两条字节码指令,所以应该成员变量会比局部变量慢都多啊,但是事实明显不是这样。
接下来我们逐步分析,我们先看 java 代码编译后的字节码
成员变量:
果然不出我们所料,成员变量的获取需要两条指令,aload_0 和 getfield。
局部变量:
局部变量也不出我们所料,只有一条指令 iload_1。
但是其实机器并不认识字节码,真正执行的是机器码,也就是我们说的汇编,每条字节码都有自己对应的汇编指令,解析执行阶段是按字节码指令逐个解释执行,但是经过 jit 后,会进行优化和编译成本地代码执行。执行原理我在公众号上分享过,有兴趣都可以翻来看看。
既然如此,我们看下真正执行的汇编代码。
成员变量:
rsi 寄存器存放都是对象地址,加上 0xc 刚好是字段 a 的地址。一条 mov 指令,完成了把对象的成员变量的值转移到寄存器 ebx 上。
局部变量:
第一个圈为把成员变量转换为局部变量,第二个是使用局部变量。可以看到使用局部变量也是一条 mov 指令就可以了。
到这里我们发现,使用局部变量和成员变量,都是一条 mov 指令,所以性能是一样的。这也跟我们前面的测试结果相吻合。但是细心的同学会发现,前面的局部变量的基准测试反而慢了一点点,原因是局部变量多了一句成员变量到局部变量到转换,但是真正在使用效率上是一样的。
其实我们刚才看的是 c1 编译后的代码,如果是 c2 完全优化后,它们是一摸一样。
成员变量:
局部变量:
可以发现它们最终的执行是个空方法,因为经过逃逸分析,无用代码消除,我们里面的变量并没有实质的作用。
到这里是不是真相了呢?不是,如果我们让代码不进行编译,完全解释执行看看。
在基准测试类上加上 @CompilerControl(CompilerControl.Mode.EXCLUDE)
可以看到,这里就符合我们最初的预期。成员变量比局部变量慢很多,差不多一个数量级。并且解释执行比 jit 后慢很多。
总结,局部变量在基于栈,解释执行的时候有优势,但是在编译成本地代码,都是基于寄存器来执行运算,它们性能就差不多了,甚至经过 jit 编译技术优化后,可能完全相同。
版权声明: 本文为 InfoQ 作者【chengmboy】的原创文章。
原文链接:【http://xie.infoq.cn/article/566fbb944eb18b327d65d6083】。文章转载请联系作者。
评论