JDKコードの実行時にJava JITはチートしますか?


405

私はいくつかのコードのベンチマークを行っていましたがjava.math.BigInteger、まったく同じアルゴリズムを使用していても、と同じくらい高速に実行することができませんでした。だから私java.math.BigIntegerは自分のパッケージにソースをコピーしてこれを試しました:

//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

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


29
面白い。1.結果は一貫していますか(またはラッキーランダム)。2. JVMをウォームアップした後で試すことはできますか?3.ランダムな要因を排除し、両方のテストの入力として同じデータセットを提供できますか?
Jigar Joshi 2017

7
JMH openjdk.java.net/projects/code-tools/jmhでベンチマークを実行してみましたか?手動で正しく測定を行うことは簡単ではありません(ウォームアップなど)。
ローマPuchkovskiy 2017

2
はい、それは非常に一貫しています。10分間実行しても、同じ違いが得られます。固定ランダムシードは、両方の実行が同じデータセットを取得することを保証します。
Koen Hendrikx 2017

5
念のため、おそらくまだJMHが必要です。そして、変更したBigIntegerをどこかに置いて、他の人がテストを再現して、実行していると思っているとおりに実行していることを確認できるようにする必要があります。
pvg 2017

回答:


529

はい、HotSpot JVMはBigInteger、Javaコードにはないいくつかのメソッドの特別なバージョンを備えているため、一種の「不正行為」です。これらのメソッドは、JVM組み込み関数と呼ばれます。

特に、BigInteger.multiplyToLenHotSpotの組み込みメソッドです。JVMソースベースには特別に手動でコード化されたアセンブリ実装がありますが、x86-64アーキテクチャのみです。

-XX:-UseMultiplyToLenIntrinsicJVMが純粋なJava実装を使用するように強制するオプションを使用して、この組み込み機能を無効にすることができます。この場合、パフォーマンスはコピーしたコードのパフォーマンスと同様になります。

PS以下は、他のHotSpot組み込みメソッドのリストです。


141

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)

その下にはメソッドの同じコードがあります-わずかに異なる命名です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.