Java JIT, JDK kodunu çalıştırırken hile yapıyor mu?


405

Bazı kodları karşılaştırıyordum java.math.BigIntegerve tam olarak aynı algoritmayı kullanırken bile, bu kadar hızlı çalıştıramadım . Bu yüzden java.math.BigIntegerkaynağı kendi paketime kopyaladım ve denedim:

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

Bunu çalıştırdığımda (MacOS'ta jdk 1.8.0_144-b01) çıktılar:

12089nsec/mul
2559044166

Ben ithalat hattı uncommented ile çalıştırdığınızda:

4098nsec/mul
2559044166

Aynı kodu kullanıyor olsa bile, BigInteger'ın JDK sürümünü sürümüme kıyasla neredeyse üç kat daha hızlıdır.

Javap ile bayt kodunu inceledim ve seçeneklerle çalışırken derleyici çıktısını karşılaştırdım:

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

ve her iki sürüm de aynı kodu oluşturuyor gibi görünüyor. Hotspot, kodumda kullanamayacağım bazı önceden hesaplanmış optimizasyonlar kullanıyor mu? Her zaman anlamadıklarını anladım. Bu farkı açıklayan nedir?


29
İlginç. 1. Sonuç tutarlı mı (ya da rastgele şanslı mı)? 2. Can JVM ısındıktan sonra deneyebilirsiniz? 3. Rastgele faktörü ortadan kaldırabilir ve her iki test için girdi ile aynı veri kümesini sağlayabilir misiniz?
Jigar Joshi

7
Karşılaştırmanızı JMH openjdk.java.net/projects/code-tools/jmh ile çalıştırmayı denediniz mi? Ölçümleri manuel olarak doğru yapmak o kadar kolay değildir (ısınmak ve tüm bu şeyler).
Roman Puchkovskiy

2
Evet, çok tutarlı. 10 dakika çalışmasına izin verirsem, yine de aynı farkı görüyorum. Sabit rastgele tohum, her iki çalışmanın da aynı veri kümesini almasını sağlar.
Koen Hendrikx

5
Muhtemelen JMH'yi hala istiyorsun, ne olur ne olmaz. Ve değiştirilmiş BigInteger'ınızı bir yere koymalısınız, böylece insanlar testinizi yeniden üretebilir ve çalıştırdığınızı düşündüğünüzü doğruladığınızı doğrulayabilir.
pvg

Yanıtlar:


529

Evet, HotSpot JVM bir tür "hile" dir, çünkü BigIntegerJava kodunda bulamayacağınız bazı yöntemlerin özel bir sürümü vardır . Bu yöntemlere JVM intrinsics denir .

Özellikle, BigInteger.multiplyToLenHotSpot'ta içkin bir yöntemdir. JVM kaynak tabanında özel bir elle kodlanmış montaj uygulaması vardır, ancak yalnızca x86-64 mimarisi için.

JVM'yi -XX:-UseMultiplyToLenIntrinsicsaf Java uygulamasını kullanmaya zorlama seçeneğiyle bu içgüdüyü devre dışı bırakabilirsiniz . Bu durumda performans, kopyalanan kodunuzun performansına benzer olacaktır.

PS İşte diğer HotSpot içsel yöntemlerinin bir listesi .


141

Gelen Java 8 bu gerçekten bir içsel bir yöntemdir; yöntemin biraz değiştirilmiş bir sürümü:

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

Şununla çalıştırılıyor:

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

Bu, çok sayıda satır yazdıracak ve bunlardan biri:

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

Öte yandan Java 9'da bu yöntem artık içsel görünmüyor, ancak içsel olan bir yöntemi çağırıyor:

 @HotSpotIntrinsicCandidate
 private static int[] implMultiplyToLen

Böylece Java 9 altında aynı kodu çalıştırmak (aynı parametrelerle) şunu ortaya çıkaracaktır:

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

Altında yöntem için aynı kod var - sadece biraz farklı bir adlandırma.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.