web-dev-qa-db-ja.com

Java JITはJDKコードを実行するときに詐欺しますか?

私はいくつかのコードをベンチマークしていました、そして、私は Java.math.BigInteger と同じくらい速く走らせることができませんでした、たとえまったく同じアルゴリズムを使ったとしても。そこで、私は Java.math.BigInteger sourceを自分のパッケージにコピーして試してみました。

//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); 
    }
}

これを実行すると(MacOSではjdk 1.8.0_144-b01)、次のように出力されます。

12089nsec/mul
2559044166

インポート行のコメントを外して実行した場合

4098nsec/mul
2559044166

たとえ まったく同じコード を使っていても、私のバージョンと比べてBigIntegerのJDKバージョンを使うときのほうが約3倍速いです。

私はjavapでバイトコードを調べ、オプション付きで実行したときのコンパイラ出力を比較しました。

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

そして両方のバージョンは同じコードを生成するようです。それでは、ホットスポットは、コードで使用できない、事前計算済みの最適化を使用していますか?そうではないことを私はいつも理解していました。この違いは何を説明しているのでしょうか。

388
Koen Hendrikx

はい、HotSpot JVMは一種の「不正」です。Javaコードには見られない特別なバージョンのBigIntegerメソッドがあるからです。これらのメソッドは JVM組み込み関数 と呼ばれます。

特に、BigInteger.multiplyToLenはHotSpotの本質的な方法です。 JVMソースベースには特別な ハンドコーディングされたアセンブリ実装 がありますが、これはx86-64アーキテクチャー専用です。

JVMに純粋なJava実装を使用させるには、-XX:-UseMultiplyToLenIntrinsicオプションを使用してこの組み込み関数を無効にします。この場合、パフォーマンスはコピーしたコードのパフォーマンスと同じになります。

P.S これは他のHotSpot組み込みメソッドの list です。

512
apangin

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>

これはたくさんの行を印刷するでしょう、そしてそれらのうちの1つは次のようになるでしょう:

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

一方、 Java 9 では、そのメソッドはもう組み込み関数ではないように見えますが、その代わりにそれは組み込み関数であるメソッドを呼び出します。

 @HotSpotIntrinsicCandidate
 private static int[] implMultiplyToLen

そのため、同じコードをJava 9で(同じパラメータで)実行すると、次のことがわかります。

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

その下には、このメソッドの同じコードがあります。名前が少し異なるだけです。

135
Eugene