Cosa rende lente le chiamate JNI?


194

So che "attraversare i confini" quando si effettua una chiamata JNI in Java è lento.

Tuttavia, voglio sapere cos'è che lo rallenta? Cosa fa l'implementazione jvm sottostante quando effettua una chiamata JNI che la rende così lenta?


2
(+1) Bella domanda. Mentre siamo in tema, vorrei incoraggiare chiunque abbia fatto benchmark reali a pubblicare i propri risultati.
NPE

2
Una chiamata JNI deve convertire gli oggetti Java passati in qualcosa che C (per esempio) può capire; lo stesso con il valore restituito. La conversione dei tipi e il marshalling dello stack di chiamate ne sono un buon pezzo.
Dave Newton,

Dave, ho capito e ne ho sentito parlare prima. Ma com'è esattamente la conversione? cos'è quel "qualcosa"? Sto cercando dettagli.
pdeva,

L'uso diretto di ByteBuffer per trasferire dati tra Java e C può comportare un sovraccarico relativamente basso.
Peter Lawrey,

6
la chiamata ha bisogno di un frame stack C corretto, spingendo tutti i registri utili della CPU (e ripristinandoli), la chiamata ha bisogno di essere recintata e impedisce anche molte ottimizzazioni come in linea. Inoltre, i thread devono lasciare il blocco dello stack di esecuzione (ad esempio per consentire ai blocchi distorti di funzionare mentre si trova nel codice nativo) e quindi ripristinarlo.
bestsss

Risposte:


174

Innanzitutto, vale la pena notare che per "lento" stiamo parlando di qualcosa che può richiedere decine di nanosecondi. Per banali metodi nativi, nel 2010 ho misurato le chiamate a una media di 40 ns sul mio desktop di Windows e 11 ns sul mio desktop di Mac. A meno che tu non faccia molte chiamate, non te ne accorgerai.

Detto questo, chiamare un metodo nativo può essere più lento rispetto a una normale chiamata al metodo Java. Le cause includono:

  • I metodi nativi non saranno delineati dalla JVM. Né saranno compilati just-in-time per questa macchina specifica - sono già compilati.
  • Un array Java può essere copiato per l'accesso nel codice nativo e successivamente copiato nuovamente. Il costo può essere lineare nella dimensione dell'array. Ho misurato la copia JNI di un array di 100.000 per una media di circa 75 microsecondi sul desktop di Windows e 82 microsecondi su Mac. Fortunatamente, l'accesso diretto può essere ottenuto tramite GetPrimitiveArrayCritical o NewDirectByteBuffer .
  • Se al metodo viene passato un oggetto o è necessario effettuare una richiamata, è probabile che il metodo nativo effettui le proprie chiamate alla JVM. L'accesso a campi, metodi e tipi Java dal codice nativo richiede qualcosa di simile alla riflessione. Le firme vengono specificate nelle stringhe e interrogate dalla JVM. Questo è sia lento che soggetto a errori.
  • Le stringhe Java sono oggetti, hanno lunghezza e sono codificate. L'accesso o la creazione di una stringa può richiedere una copia O (n).

Qualche discussione aggiuntiva, forse datata, può essere trovata in "Prestazioni della piattaforma Java: strategie e tattiche", 2000, di Steve Wilson e Jeff Kesselman, nella sezione "9.2: Esame dei costi JNI". Si trova a circa un terzo di questa pagina , fornita nel commento di @Philip di seguito.

Il documento IBM DeveloperWorks 2009 "Best practice per l'utilizzo di Java Native Interface" fornisce alcuni suggerimenti per evitare insidie ​​sulle prestazioni con JNI.


1
Questa risposta afferma che JVM può incorporare un codice nativo .
AH,

5
Questa risposta rileva che nella JVM è incorporato un codice nativo standard anziché utilizzare JNI. Sopra, "metodi nativi" si riferisce al caso generale di metodi nativi definiti dall'utente implementati tramite JNI. Grazie per il puntatore a sun.misc.Unsafe.
Andy Thomas,

Non volevo affermare che questo approccio può essere utilizzato per ogni chiamata JNI. Ma non farà male a sapere che ci sia qualche via di mezzo tra il bytecode puro e il codice JNI puro. Forse questo influenzerà alcune decisioni di progettazione. Forse questo meccanismo sarà generalizzato in futuro.
AH,

3
@AH, sbagli intrinsecamente con JNI. Sono abbastanza diversi. sun.misc.Unsafee molte altre cose come System.currentTimeMillis/nanoTimesono gestite tramite "magia" dalla JVM. Non sono JNI e non hanno affatto file .c / .h adeguati, a parte l'impl JVM stesso. L'approccio non può essere seguito se non stai scrivendo / hackerando la JVM.
bestsss

1
" questo documento java.sun.com " è attualmente non funzionante, ecco un link funzionante.
Philip Guin,

25

Vale la pena ricordare che non tutti i metodi Java contrassegnati con nativesono "lenti". Alcuni di essi sono intrinseci che li rendono estremamente veloci. Per controllare quali sono intrinseci e quelli che non lo sono, si può cercare do_intrinsicin vmSymbols.hpp .


23

Fondamentalmente la JVM costruisce in modo interpretativo i parametri C per ogni chiamata JNI e il codice non è ottimizzato.

Ci sono molti altri dettagli delineati in questo documento

Se sei interessato al benchmarking di JNI rispetto al codice nativo, questo progetto ha un codice per l'esecuzione di benchmark.


2
il documento a cui ti sei collegato sembra più un documento di riferimento sulle prestazioni che uno che descriva come JNI funzioni internamente.
pdeva,

@pdeva Purtroppo le altre risorse che ho trovato erano collegate a java.sun.com e i collegamenti non sono stati aggiornati dall'acquisizione di Oracle. Sto cercando maggiori dettagli sugli interni di JNI.
dmck,

13
Il documento parla di Java 1.3 - molto tempo fa. I problemi di quel tempo si applicano ancora a Java 7?
AH,
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.