JAVA對於乘法除法和模運算的優化,是否需要轉換成位運算
最近思考一個問題。我們知道,在底層彙編程式碼中,除以2的指令效率遠低於直接右移1位。所以我看到的不止一個java教學視訊(原諒我看了很多民間流傳的教學視訊,簡單粗暴)說過/2儘量寫成>>1。但是另一方面,我記得上課學過編譯器的優化問題,很多事情其實是不需要程式設計師考慮的。那麼事實是怎麼樣的呢?
這就要考慮到java編譯的流程了:.java檔案先轉換成.class檔案(位元組碼),在執行的時候,JVM先接收到位元組碼,再做JIT即時優化和編譯,形成對應的機器碼。
首先我想到的是,先檢視一下.class檔案的位元組碼,看看是否進行了優化:
1 |
javap -verbose 檔名 //這條指令可以反編譯.calss檔案,檢視到具體指令。 |
最終看到的結果,除法被編譯成了idiv,右移被編譯成了ishr。就是說並沒有優化。(如果是常數運算會直接在編譯位元組碼時常量摺疊,此處就不考慮了)
程式碼的測試結果也印證了這一點:
1 |
long a1=System.currentTimeMillis(); |
運算結果:
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 |
int low = fromIndex; |
這裡用(low + high) >>> 1代替(low + high) /2是非常正確的,首先是因為陣列下標肯定不會是負數,另一方面如果low + high大於int最大值(溢位變為負數了)時,只有>>>1能保證結果正確。