2017-08-28 57 views
363

我正在對一些代碼進行基準測試,即使使用完全相同的算法,我也無法使其運行速度與java.math.BigInteger一樣快。 所以我複製java.math.BigInteger源到我自己的包,並試圖此:運行JDK代碼時,Java JIT會作弊嗎?

//import java.math.BigInteger; 

public class MultiplyTest { 
    public static void main(String[] args) { 
     Random r = new Random(1); 
     long tm = 0, count = 0,result=0; 
     for (int i = 0; i < 400000; i++) { 
      int s1 = 400, s2 = 400; 
      BigInteger a = new BigInteger(s1 * 8, r), b = new BigInteger(s2 * 8, r); 
      long tm1 = System.nanoTime(); 
      BigInteger c = a.multiply(b); 
      if (i > 100000) { 
       tm += System.nanoTime() - tm1; 
       count++; 
      } 
      result+=c.bitLength(); 
     } 
     System.out.println((tm/count) + "nsec/mul"); 
     System.out.println(result); 
    } 
} 

當我運行這個(在Mac OS JDK 1.8.0_144-B01)則輸出:

12089nsec/mul 
2559044166 

當我運行導入行註釋掉:

4098nsec/mul 
2559044166 

它幾乎快三倍使用BigInteger的JDK版本與我的版本時,即使它使用完全相同的碼。

的選項運行時,我已經審查了javap的字節碼,並與編譯器輸出:

-Xbatch -XX:-TieredCompilation -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions 
-XX:+PrintInlining -XX:CICompilerCount=1 

和兩個版本似乎產生相同的代碼。 那麼,熱點使用一些預先計算的優化,我不能在我的代碼中使用?我一直都明白他們沒有。 這個區別是什麼解釋?

+25

有趣。 1.結果是否一致(或只是幸運的隨機)? 2.您可以嘗試升溫JVM之後嗎? 3.您是否可以消除隨機因素,並提供相同的數據集作爲測試的輸入? –

+7

您是否嘗試使用JMH運行您的基準測試http://openjdk.java.net/projects/code-tools/jmh/?手動進行測量並不是那麼容易(預熱和所有這些)。 –

+2

是的,它非常一致。如果我讓它運行10分鐘,我仍然會得到同樣的區別。固定的隨機種子確保兩次運行獲得相同的數據集。 –

回答

490

是的,HotSpot JVM是一種「作弊」,因爲它有一些在Java代碼中找不到的方法的特殊版本BigInteger。這些方法被稱爲JVM intrinsics

特別是,BigInteger.multiplyToLen是HotSpot中的內在方法。 JVM源代碼中有一個特殊的hand-coded assembly implementation,但只適用於x86-64體系結構。

您可以通過-XX:-UseMultiplyToLenIntrinsic選項禁用此內在選項以強制JVM使用純Java實現。在這種情況下,性能將與複製代碼的性能類似。

P.S.這是其他HotSpot內在方法的list

121

的Java 8這確實是一種內在的,該方法的一個稍作修改的版本:

private static BigInteger test() { 

    Random r = new Random(1); 
    BigInteger c = null; 
    for (int i = 0; i < 400000; i++) { 
     int s1 = 400, s2 = 400; 
     BigInteger a = new BigInteger(s1 * 8, r), b = new BigInteger(s2 * 8, r); 
     c = a.multiply(b); 
    } 
    return c; 
} 

與運行此:

java -XX:+UnlockDiagnosticVMOptions 
     -XX:+PrintInlining 
     -XX:+PrintIntrinsics 
     -XX:CICompilerCount=2 
     -XX:+PrintCompilation 
     <YourClassName> 

這將打印大量線路和一個他們將是:

java.math.BigInteger::multiplyToLen (216 bytes) (intrinsic) 

的Java 9,另一方面這種方法似乎不是一個內在的了,但反過來它調用是一種內在的方法:

@HotSpotIntrinsicCandidate 
private static int[] implMultiplyToLen 

所以運行Java 9在相同的代碼(用同樣的參數)將顯示:

java.math.BigInteger::implMultiplyToLen (216 bytes) (intrinsic) 

下面是該方法相同的代碼 - 只是一個稍微不同的命名。