web-dev-qa-db-fra.com

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

Je comparais certains codes et je ne pouvais pas le faire fonctionner aussi rapidement qu'avec Java.math.BigInteger , même avec le même algorithme. Alors j'ai copié Java.math.BigInteger source dans mon propre paquet et j'ai 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?

394
Koen Hendrikx

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 s'appellent JVM intrinsics .

En particulier, BigInteger.multiplyToLen est une méthode intrinsèque dans HotSpot. Il existe une --- implémentation d'assemblage codée à la main 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 machine virtuelle Java à utiliser l’implémentation pure Java. Dans ce cas, les performances seront similaires à celles de votre code copié.

P.S. Voici une liste d'autres méthodes intrinsèques HotSpot.

518
apangin

Dans 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)

Dans Java 9, d’autre part, 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.

136
Eugene