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.