¿Hace trampa Java JIT al ejecutar código JDK?


Yo estaba benchmarking algo de código, y yo no podía llegar a correr tan rápido como con java.math.BigInteger, incluso cuando se utiliza el mismo algoritmo. Así que copié java.math.BigInteger fuente en mi propio paquete y probado 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 corro esto (jdk 1.8.0_144-b01 en macOS) sale:

12089nsec/mul
2559044166

Cuando lo corro con la línea import uncomented:

4098nsec/mul
2559044166

Es casi tres veces más rápido cuando se utiliza la versión JDK de BigInteger frente a mi versión, incluso si está utilizando el exacto el mismo código.

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, ¿hotspot está usando algunas optimizaciones precalculadas que no puedo usar en mi código? Siempre entendí que no lo hacen. ¿Qué explica esta diferencia?

Author: Sufian, 2017-08-28

2 answers

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 JVM intrínsecos .

En particular, BigInteger.multiplyToLen es el método instrinsic en HotSpot. Hay una implementación especial de ensamblado codificado a mano en la base fuente JVM, pero solo para la arquitectura x86-64.

Puede desactivar este instrinsic con la opción -XX:-UseMultiplyToLenIntrinsic para forzar a JVM a usar implementación Java pura. En este caso la el rendimiento será similar al rendimiento del código copiado.

P.d. Aquí hay una lista de otros métodos intrínsecos de HotSpot.

 497
Author: apangin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-28 07:31:55

En Java 8 esto es de hecho una versión intrínseca, 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 parece no ser un intrínseco más, pero a su vez llama a un método que es un intrínseco:

 @HotSpotIntrinsicCandidate
 private static int[] implMultiplyToLen

Así que ejecutar el mismo código bajo 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.

 127
Author: Eugene,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-02 10:28:10