您的位置:首页 > 编程语言 > Java开发

JAVA对于乘法除法和模运算的优化,是否需要转换成位运算

2015-04-21 15:23 363 查看
最近思考一个问题。我们知道,在底层汇编代码中,除以2的指令效率远低于直接右移1位。所以我看到的不止一个java教学视频(原谅我看了很多民间流传的教学视频,简单粗暴)说过/2尽量写成>>1。但是另一方面,我记得上课学过编译器的优化问题,很多事情其实是不需要程序员考虑的。那么事实是怎么样的呢?

这就要考虑到java编译的流程了:.java文件先转换成.class文件(字节码),在运行的时候,JVM先接收到字节码,再做JIT即时优化和编译,形成对应的机器码。

首先我想到的是,先查看一下.class文件的字节码,看看是否进行了优化:
1

javap -verbose 文件名 //这条指令可以反编译.calss文件,查看到具体指令。

最终看到的结果,除法被编译成了idiv,右移被编译成了ishr。就是说并没有优化。(如果是常数运算会直接在编译字节码时常量折叠,此处就不考虑了)

代码的测试结果也印证了这一点:
12
3
4
5
6

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没有把除法直接优化成右移呢,因为对于负数来说,右移不等于/2。举个例子:

-5 / 2 = -2

-5 >> 1 = -3

-5 >>> 1 = 2147483645

变量在编译期间如果再加一次正负数判断,往往是得不偿失的。因此:

对于乘法和以及%运算,JVM一定会优化,这些是不需要程序员去考虑的,直接去用*/%即可。

对于除法,因为上述问题,确实是位移更快些。

最后引申一下,虽然我们要“充分相信编译器”,但有些时候右移可能是最佳选择,例如java.util.Arrays.binarySearch:
12
3
4
5
6
7
8
9
10
1112
13
14
15

int low = fromIndex;
int high = toIndex - 1;

while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = a[mid];

if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1);  // key not found.

这里用(low + high) >>> 1代替(low + high) /2是非常正确的,首先是因为数组下标肯定不会是负数,另一方面如果low + high大于int最大值(溢出变为负数了)时,只有>>>1能保证结果正确。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐