2017-08-28 74 views
363

Byłem benchmarking niektórych kodu i nie mogłem go uruchomić tak szybko, jak z java.math.BigInteger, nawet przy użyciu tego samego algorytmu. Więc kopiowane java.math.BigInteger źródło w moim opakowaniu i próbowałem to:Czy Java JIT oszukuje, gdy działa kod JDK?

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

Gdy ten (JDK 1.8.0_144-B01 na MacOS) wyprowadza:

12089nsec/mul 
2559044166 

Kiedy uruchamiam go z linia import komentarzem:

4098nsec/mul 
2559044166 

to prawie trzy razy szybciej przy użyciu wersji JDK z BigInteger porównaniu mojej wersji, nawet jeśli używa dokładnie to samo kod.

Mam zbadaniu kodu bajtowego z javap, aw porównaniu wyjście kompilatora, gdy uruchomiony z opcjami:

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

i obie wersje wydają się wygenerować ten sam kod. Czy jest więc hotspot za pomocą niektórych wstępnie obliczonych optymalizacji, których nie mogę użyć w moim kodzie? Zawsze rozumiałem, że nie. Co wyjaśnia tę różnicę?

+25

Interesujące. 1. Czy wynik jest spójny (czy tylko losowy losowy)? 2. Czy możesz spróbować nagrzać JVM? 3. Czy można wyeliminować czynnik losowy i podać ten sam zestaw danych jako dane wejściowe dla obu testów? –

+7

Czy próbowałeś uruchomić swój benchmark z JMH http://openjdk.java.net/projects/code-tools/jmh/? Ręczne pomiary nie są takie proste (rozgrzanie i tak dalej). –

+2

Tak, to jest bardzo spójne. Jeśli pozwolę mu działać przez 10 minut, nadal dostanę tę samą różnicę. Ustalony losowy materiał siewny zapewnia, że ​​obie serie otrzymują ten sam zestaw danych. –

Odpowiedz

490

Tak, HotSpot JVM jest swego rodzaju "oszukiwaniem", ponieważ ma specjalną wersję niektórych metod, których nie można znaleźć w kodzie Java. Te metody są nazywane JVM intrinsics.

W szczególności, BigInteger.multiplyToLen jest metodą instrinetyczną w HotSpot. W źródle źródłowym JVM istnieje specjalna hand-coded assembly implementation, ale tylko dla architektury x86-64.

Możesz wyłączyć tę opcję z opcją -XX:-UseMultiplyToLenIntrinsic, aby zmusić JVM do korzystania z czystej implementacji Java. W takim przypadku wydajność będzie podobna do wydajności skopiowanego kodu.

P.S. Oto list innych wewnętrznych metod HotSpot.

121

W Java 8 to rzeczywiście nieodłącznym, lekko zmodyfikowana wersja metody:

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

Running to z:

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

To drukować wiele wierszy i jeden z będą one następujące:

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

W Java 9 z drugiej strony ta metoda wydaje się nie być już nieodłącznym, ale z kolei wywołuje metodę, która jest nieodłącznym:

@HotSpotIntrinsicCandidate 
private static int[] implMultiplyToLen 

Tak działa ten sam kod pod Java 9 (z tymi samymi parametrami) ujawni:

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

Pod tym samym kodem dla tej metody - tylko nieco inna nazwa.

Powiązane problemy