1. 程式人生 > >JAVA對於乘法除法和模運算的優化,是否需要轉換成位運算

JAVA對於乘法除法和模運算的優化,是否需要轉換成位運算

最近思考一個問題。我們知道,在底層彙編程式碼中,除以2的指令效率遠低於直接右移1位。所以我看到的不止一個java教學視訊(原諒我看了很多民間流傳的教學視訊,簡單粗暴)說過/2儘量寫成>>1。但是另一方面,我記得上課學過編譯器的優化問題,很多事情其實是不需要程式設計師考慮的。那麼事實是怎麼樣的呢?

這就要考慮到java編譯的流程了:.java檔案先轉換成.class檔案(位元組碼),在執行的時候,JVM先接收到位元組碼,再做JIT即時優化和編譯,形成對應的機器碼。

首先我想到的是,先檢視一下.class檔案的位元組碼,看看是否進行了優化:

1
javap -verbose 檔名 //這條指令可以反編譯.calss檔案,檢視到具體指令。

最終看到的結果,除法被編譯成了idiv,右移被編譯成了ishr。就是說並沒有優化。(如果是常數運算會直接在編譯位元組碼時常量摺疊,此處就不考慮了)

程式碼的測試結果也印證了這一點:

1
2
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:

1
2
3
4
5
6
7
8
9
10
11

12
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能保證結果正確。