Java JIT tradisce quando si esegue il codice JDK?


405

Stavo confrontando un po 'di codice e non riuscivo a farlo funzionare così velocemente java.math.BigInteger, anche quando usavo lo stesso algoritmo. Quindi ho copiato l' java.math.BigIntegerorigine nel mio pacchetto e ho provato questo:

//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); 
    }
}

Quando eseguo questo (jdk 1.8.0_144-b01 su MacOS) viene visualizzato:

12089nsec/mul
2559044166

Quando lo eseguo con la riga di importazione senza commento:

4098nsec/mul
2559044166

È quasi tre volte più veloce quando si utilizza la versione JDK di BigInteger rispetto alla mia versione, anche se utilizza esattamente lo stesso codice.

Ho esaminato il bytecode con javap e confrontato l'output del compilatore durante l'esecuzione con le opzioni:

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

ed entrambe le versioni sembrano generare lo stesso codice. Quindi l'hotspot sta usando alcune ottimizzazioni pre-calcolate che non posso usare nel mio codice? Ho sempre capito che non lo fanno. Cosa spiega questa differenza?


29
Interessante. 1. Il risultato è coerente (o solo fortunato a caso)? 2. Puoi provare dopo aver riscaldato JVM? 3. È possibile eliminare il fattore casuale e fornire lo stesso set di dati di input per entrambi i test?
— Jigar Joshi,

7
Hai provato a eseguire il tuo benchmark con JMH openjdk.java.net/projects/code-tools/jmh ? Non è così facile effettuare le misurazioni correttamente manualmente (riscaldamento e tutto il resto).
— Roman Puchkovskiy,

2
Sì, è molto coerente. Se lo lascio funzionare per 10 minuti, ottengo comunque la stessa differenza. Il seed casuale fisso assicura che entrambe le serie ottengano lo stesso set di dati.
— Koen Hendrikx,

5
Probabilmente vuoi ancora JMH, per ogni evenienza. E dovresti installare il tuo BigInteger modificato da qualche parte in modo che le persone possano riprodurre il tuo test e verificare che stai eseguendo ciò che pensi di essere in esecuzione.
— pvg

Risposte:


529

Sì, HotSpot JVM è una specie di "imbroglione", perché ha una versione speciale di alcuni BigIntegermetodi che non troverai nel codice Java. Questi metodi sono chiamati intrinseci JVM .

In particolare, BigInteger.multiplyToLenè un metodo instrinsic in HotSpot. Esiste un'implementazione speciale di assembly con codifica manuale nella base di origine JVM, ma solo per l'architettura x86-64.

È possibile disabilitare questa istruzione con l' -XX:-UseMultiplyToLenIntrinsicopzione per forzare JVM a utilizzare l'implementazione Java pura. In questo caso, le prestazioni saranno simili a quelle del codice copiato.

PS Ecco un elenco di altri metodi intrinseci di HotSpot.


141

In Java 8 questo è davvero un metodo intrinseco; una versione leggermente modificata del metodo:

 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;
}

Eseguendo questo con:

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

Questo stamperà molte linee e una di queste sarà:

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

In Java 9 invece questo metodo sembra non essere più un intrinseco, ma a sua volta chiama un metodo intrinseco:

 @HotSpotIntrinsicCandidate
 private static int[] implMultiplyToLen

Quindi l'esecuzione dello stesso codice in Java 9 (con gli stessi parametri) rivelerà:

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

Sotto c'è lo stesso codice per il metodo - solo una denominazione leggermente diversa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.