virtuelle - Est-ce que Java JIT triche lors de l'exécution de code JDK?




wiki jvm (2)

Je comparais certains codes et je ne pouvais pas le faire fonctionner aussi vite qu'avec java.math.BigInteger , même avec le même algorithme. J'ai donc copié le java.math.BigInteger source java.math.BigInteger dans mon propre paquet et essayé ceci:

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

Quand je lance ceci (jdk 1.8.0_144-b01 sur MacOS), il génère:

12089nsec/mul
2559044166

Quand je le lance avec la ligne d'importation non commentée:

4098nsec/mul
2559044166

C'est presque trois fois plus vite lorsque j'utilise la version JDK de BigInteger par rapport à ma version, même si elle utilise exactement le même code .

J'ai examiné le bytecode avec javap et comparé les résultats du compilateur lors de l'exécution avec des options:

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

et les deux versions semblent générer le même code. Hotspot utilise-t-il des optimisations précalculées que je ne peux pas utiliser dans mon code? J'ai toujours compris que non. Qu'est-ce qui explique cette différence?


En Java 8, il s’agit bien d’une méthode intrinsèque; une version légèrement modifiée de la méthode:

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

Lancer ceci avec:

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

Cela va imprimer beaucoup de lignes et l'une d'entre elles sera:

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

En revanche, sous Java 9 , cette méthode ne semble plus être intrinsèque, mais elle appelle à son tour une méthode qui est intrinsèque:

 @HotSpotIntrinsicCandidate
 private static int[] implMultiplyToLen

Donc, exécuter le même code sous Java 9 (avec les mêmes paramètres) révélera:

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

En dessous, le même code pour la méthode - juste un nom légèrement différent.


Oui, HotSpot JVM est une sorte de "triche", car il a une version spéciale de certaines méthodes BigInteger que vous ne trouverez pas dans le code Java. Ces méthodes sont appelées intrinsèques JVM .

BigInteger.multiplyToLen est une méthode spécifique dans HotSpot. Il existe une implémentation d'assemblage codée à la main spéciale dans la base source JVM, mais uniquement pour l'architecture x86-64.

Vous pouvez désactiver cette instruction avec l'option -XX:-UseMultiplyToLenIntrinsic pour forcer la JVM à utiliser l'implémentation Java pure. Dans ce cas, les performances seront similaires à celles de votre code copié.

PS Voici une list des autres méthodes intrinsèques HotSpot.





jvm-hotspot