Apakah Java JIT curang ketika menjalankan kode JDK?

405

Saya membandingkan beberapa kode, dan saya tidak bisa menjalankannya secepat java.math.BigInteger, bahkan ketika menggunakan algoritma yang sama persis. Jadi saya menyalin java.math.BigIntegersumber ke paket saya sendiri dan mencoba ini:

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

Ketika saya menjalankan ini (jdk 1.8.0_144-b01 pada MacOS) itu output:

12089nsec/mul
2559044166

Ketika saya menjalankannya dengan jalur impor tanpa komentar:

4098nsec/mul
2559044166

Ini hampir tiga kali lebih cepat ketika menggunakan versi JDK BigInteger versus versi saya, bahkan jika itu menggunakan kode yang sama persis.

Saya telah memeriksa bytecode dengan javap, dan membandingkan keluaran kompiler ketika dijalankan dengan opsi:

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

dan kedua versi tampaknya menghasilkan kode yang sama. Jadi, apakah hotspot menggunakan beberapa optimisasi yang dikomputasi yang tidak dapat saya gunakan dalam kode saya? Saya selalu mengerti bahwa mereka tidak. Apa yang menjelaskan perbedaan ini?

Koen Hendrikx
sumber
29
Menarik. 1. Apakah hasilnya konsisten (atau hanya keberuntungan acak)? 2. Bisakah Anda mencoba setelah pemanasan JVM? 3. Bisakah Anda menghilangkan faktor acak dan memberikan dataset yang sama sebagai input untuk kedua tes?
Jigar Joshi
7
Apakah Anda mencoba menjalankan patokan Anda dengan JMH openjdk.java.net/projects/code-tools/jmh ? Tidak mudah untuk melakukan pengukuran dengan benar secara manual (pemanasan dan semua itu).
Roman Puchkovskiy
2
Ya itu sangat konsisten. Jika saya biarkan berjalan selama 10 menit saya masih mendapatkan perbedaan yang sama. Benih acak tetap memastikan bahwa kedua menjalankan mendapatkan dataset yang sama.
Koen Hendrikx
5
Anda mungkin masih menginginkan JMH, untuk jaga-jaga. Dan Anda harus memasang BigInteger Anda yang telah dimodifikasi di suatu tempat sehingga orang dapat mereproduksi tes Anda dan memverifikasi bahwa Anda menjalankan apa yang Anda pikir sedang Anda jalankan.
pvg

Jawaban:

529

Ya, HotSpot JVM adalah semacam "curang", karena ia memiliki versi khusus dari beberapa BigIntegermetode yang tidak akan Anda temukan dalam kode Java. Metode ini disebut intrinsik JVM .

Secara khusus, BigInteger.multiplyToLenadalah metode instrinsik di HotSpot. Ada implementasi perakitan kode tangan khusus di basis sumber JVM, tetapi hanya untuk arsitektur x86-64.

Anda dapat menonaktifkan instrinsik ini dengan -XX:-UseMultiplyToLenIntrinsicopsi untuk memaksa JVM untuk menggunakan implementasi Java murni. Dalam hal ini kinerjanya akan mirip dengan kinerja kode yang Anda salin.

PS Berikut adalah daftar metode intrinsik HotSpot lainnya.

apangin
sumber
141

Di Java 8 ini memang merupakan metode intrinsik; versi metode yang sedikit dimodifikasi:

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

Menjalankan ini dengan:

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

Ini akan mencetak banyak baris dan salah satunya adalah:

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

Di Jawa 9 di sisi lain metode yang tampaknya tidak menjadi intrinsik lagi, tetapi pada gilirannya itu memanggil metode yang intrinsik:

 @HotSpotIntrinsicCandidate
 private static int[] implMultiplyToLen

Jadi menjalankan kode yang sama di bawah Java 9 (dengan parameter yang sama) akan mengungkapkan:

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

Di bawahnya adalah kode yang sama untuk metode - hanya penamaan yang sedikit berbeda.

Eugene
sumber