2017-08-28 5 views
363

Je faisais du benchmarking sur du code, et je n'arrivais pas à le faire fonctionner aussi vite qu'avec java.math.BigInteger, même en utilisant exactement le même algorithme. Je copié java.math.BigInteger source dans mon propre paquet et essayé ceci:Est-ce que Java JIT triche lors de l'exécution du code 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); 
    } 
} 

Quand je lance ce (jdk 1.8.0_144-B01 sur MacOS) il émet:

12089nsec/mul 
2559044166 

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

4098nsec/mul 
2559044166 

il est presque trois fois plus vite lorsque vous utilisez la version de JDK BigInteger par rapport à ma version, même si elle est en utilisant exactement la même code.

J'ai examiné le bytecode avec javap, et la sortie du compilateur par rapport lors de l'exécution avec des options:

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

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

+25

Intéressant. 1. Le résultat est-il cohérent (ou aléatoire)? 2. Pouvez-vous essayer après avoir réchauffé JVM? 3. Pouvez-vous éliminer le facteur aléatoire et fournir le même ensemble de données en entrée pour le test? –

+7

Avez-vous essayé d'exécuter votre benchmark avec JMH http://openjdk.java.net/projects/code-tools/jmh/? Il n'est pas si simple de faire des mesures correctement manuellement (échauffement et tout ça). –

+2

Oui, c'est très cohérent. Si je laisse courir pendant 10 minutes, je reçois toujours la même différence. La graine aléatoire fixe garantit que les deux exécutions obtiennent le même jeu de données. –

Répondre

490

Oui, HotSpot JVM est une sorte de "tricherie", car elle possède une version spéciale de certaines méthodes BigInteger que vous ne trouverez pas dans le code Java. Ces méthodes sont appelées JVM intrinsics.

En particulier, BigInteger.multiplyToLen est méthode intrinsèque dans HotSpot. Il existe une base source JVM spéciale hand-coded assembly implementation, mais uniquement pour l'architecture x86-64.

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

P.S. Voici une autre méthode intrinsèque HotSpot: list.

121

Dans Java 8 c'est en effet une valeur 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; 
} 

L'exécution de ce avec:

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

Ce imprimera beaucoup de lignes et l'une des ils seront:

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

Dans Java 9 d'autre part cette méthode semble ne pas être une plus intrinsèque, mais à son tour il appelle une méthode qui est une valeur 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, c'est le même code pour la méthode - juste un nom légèrement différent.