java - jdk - netbeans
¿Java JIT engaña cuando ejecuta código JDK? (2)
En Java 8, este es de hecho un método intrínseco; Una versión ligeramente modificada del método:
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;
}
Ejecutando esto con:
java -XX:+UnlockDiagnosticVMOptions
-XX:+PrintInlining
-XX:+PrintIntrinsics
-XX:CICompilerCount=2
-XX:+PrintCompilation
<YourClassName>
Esto imprimirá muchas líneas y una de ellas será:
java.math.BigInteger::multiplyToLen (216 bytes) (intrinsic)
En Java 9, por otro lado, ese método ya no parece ser intrínseco, pero a su vez llama a un método que es intrínseco:
@HotSpotIntrinsicCandidate
private static int[] implMultiplyToLen
Por lo tanto, ejecutar el mismo código en Java 9 (con los mismos parámetros) revelará:
java.math.BigInteger::implMultiplyToLen (216 bytes) (intrinsic)
Debajo está el mismo código para el método, solo un nombre ligeramente diferente.
Estaba comparando un código, y no pude lograr que se ejecutara tan rápido como con
java.math.BigInteger
, incluso cuando usaba exactamente el mismo algoritmo.
Así que copié la fuente
java.math.BigInteger
en mi propio paquete y probé esto:
//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);
}
}
Cuando ejecuto esto (jdk 1.8.0_144-b01 en MacOS) sale:
12089nsec/mul
2559044166
Cuando lo ejecuto con la línea de importación sin comentarios:
4098nsec/mul
2559044166
Es casi tres veces más rápido cuando uso la versión JDK de BigInteger en comparación con mi versión, incluso si usa el mismo código exacto .
He examinado el bytecode con javap y comparado la salida del compilador cuando se ejecuta con opciones:
-Xbatch -XX:-TieredCompilation -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions
-XX:+PrintInlining -XX:CICompilerCount=1
y ambas versiones parecen generar el mismo código. Entonces, ¿el punto de acceso utiliza algunas optimizaciones precalculadas que no puedo usar en mi código? Siempre entendí que no. ¿Qué explica esta diferencia?
Sí, HotSpot JVM es una especie de "trampa", porque tiene una versión especial de algunos métodos
BigInteger
que no encontrará en el código Java.
Estos métodos se llaman
intrínsecos JVM
.
En particular,
BigInteger.multiplyToLen
es un método intrínseco en HotSpot.
Existe una
implementación
especial
de ensamblaje codificado a mano
en la base de origen JVM, pero solo para la arquitectura x86-64.
Puede deshabilitar este instrínseco con la opción
-XX:-UseMultiplyToLenIntrinsic
para forzar a JVM a usar la implementación pura de Java.
En este caso, el rendimiento será similar al rendimiento de su código copiado.
PD Aquí hay una list de otros métodos intrínsecos de HotSpot.