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?
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.
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.