最近思考一个问题。我们知道,在底层汇编代码中,除以2的指令效率远低于直接右移1位。所以我看到的不止一个java教学视频(原谅我看了很多民间流传的教学视频,简单粗暴)说过/2尽量写成>>1。但是另一方面,我记得上课学过编译器的优化问题,很多事情其实是不需要程序员考虑的。那么事实是怎么样的呢?
这就要考虑到java编译的流程了:.java文件先转换成.class文件(字节码),在运行的时候,JVM先接收到字节码,再由JIT做即时优化和编译,形成对应的机器码。
首先我想到的是,先查看一下.class文件的字节码,看看是否进行了优化:
javac -p 文件名 //这条指令可以反编译.calss文件,查看到具体指令。
最终看到的结果,除法被编译成了idiv,右移被编译成了ishr。就是说并没有优化。
另外,我又尝试写了一些比较简单的带有冗余计算的代码,反编译之后发现同样没有优化,可以近似的认为编译到字节码是“直译”。
但是这并不能意味着/2的效率低于>>1,我用eclipse写代码尝试了一下:
long a1=System.currentTimeMillis();
for (long i = 0; i < 999999999l; i++) {
j=123456>>1;//或j=123456/2;
}
long a2=System.currentTimeMillis();
System.out.println(a2-a1);
多次运行发现:
j=i/2; 347ms
j=i>>1; 349ms
用javac直接编译运行发现结果相同。就是说,常数的位移操作和除法操作效率相同。
为此继续在stackoverflow查了一下资料,得到的结果毫无争议的都建议采用除法而不是位移,使用位移是一种premature optimization。因为JIT可以做到优化(不仅是除以2的乘方,任何数有可能会被拆成位移和加法的组合),而至于究竟是否会把除法变成位移取决于JVM所在环境的CPU类型,JVM通常来说都会选择最优的方式处理的。
参考资料:
http://stackoverflow.com/questions/1514949/quick-java-optimization-question
顺便再说一下。我把上面的代码稍作修改:
long a1=System.currentTimeMillis();
for (long i = 0; i < 999999999l; i++) {
j=i>>1;//或j=i/2;
}
long a2=System.currentTimeMillis();
System.out.println(a2-a1);
j=i/2; 1070ms
j=i>>1; 702ms
结果发现除法操作会比位移操作慢很多。原因在于对于负偶数,右移不等于除法。对于被除数是变量的情况下,并不会优化。
下面总结一下:
对于乘法和常量的除法以及%运算,JVM一定会优化,这些是不需要程序员去考虑的,直接去用*/%即可。
对于变量的除法,因为上述问题,确实是位移更快些。