My doubt is: since all the java code is converted to native in android
is there any use of JNI/NDK and what is it?
Il controllo sulla memoria è il più grande per me. Ho visto Java fare un lavoro molto competente di assegnazione dei registri e selezione delle istruzioni, ma c'è una evidente difficoltà per le prestazioni, e questo è il sovraccarico associato agli oggetti e la perdita che ti dà il layout della memoria e i modelli di accesso. Tuttavia, puoi rendere il codice Java molto più performante del solito e recuperare gran parte del controllo perduto se puoi appoggiarti più pesantemente su semplici vecchi tipi di dati e non su oggetti.
Ad esempio, in C puoi fare cose come questa:
struct Vector
{
// AoS
float xyzw[4];
};
E crea una matrice di questi ed assicurati che i contenuti saranno contigui con un passo che è sempre sizeof(struct Vector)
che generalmente escluderà padding in questo caso e sarà solo sizeof(float)*4
o esattamente 128 bit. È inoltre possibile eseguire l'heap dell'allocazione allineata ai limiti di 128 bit e quindi essere in grado di utilizzare le mosse allineate nei registri SIMD e di vettorializzare il codice con intrinseche efficienti. Una cosa simile in C ++ in cui è possibile creare una classe vettoriale con metodi e non pagare alcun overhead per comodità e garantire comunque che una serie di essi sia contigua.
Tuttavia, se provi a creare una classe Vector
in Java, non ottieni alcun controllo di questo tipo, ogni oggetto Vector
avrà alcune meta informazioni aggiuntive associate a cose come reflection e dynamic dispatch che gonfieranno le dimensioni di a Vector
. Se provi a creare un array di questi, sarà simile alla creazione di un array di puntatori, a quel punto dovrai pagare memoria aggiuntiva e costi indiretti dei puntatori in cima ai metadati della classe per ogni istanza Vector
, e i contenuti dell'array non sarà garantito che sia contiguo (probabilmente sarà inizialmente se allocate ogni singolo Vector
in sequenza tutto in una volta, ma dopo il primo ciclo GC, possono essere frammentati in memoria). Quello può essere un killer delle prestazioni con la perdita della località temporale e soprattutto spaziale, che porta a molti più errori di cache del necessario.
Detto questo, spesso c'è molto spazio per il codice Java per andare molto più velocemente. Se si lavora con un gigantesco array di float
, ad esempio, invece di un array di Vector
, si evita tutto il sovraccarico sopra e si può garantire che il contenuto dell'array rimanga contiguo. In realtà penso che molte persone stiano a rendere le loro applicazioni Java molto performanti senza raggiungere il JNI se solo potessero lavorare con array di semplici vecchi tipi di dati, non di oggetti, per le aree che sono tentate di implementare in JNI. Per comodità puoi creare una classe Vectors
che memorizza un gruppo di vettori contemporaneamente (l'overhead degli oggetti sarà quindi banale se è pagato solo una volta per cento vettori, ad esempio, e non per vettore) e fornisce operazioni su di essi, ma internamente rappresenta solo quei vettori come un grande array di float
.
Ho persino visto un ragionevole tracciatore interattivo del percorso della CPU implementato in Java (nessuna chiamata API nativa per il tracciamento del percorso stesso, incluso BVH). È stato sorprendentemente veloce, soprattutto per un giovane progetto amatoriale da un uomo, ma quando ho guardato il codice sorgente, è esattamente quello che ha fatto. Evitava oggetti per dati caldi critici dal punto di vista delle prestazioni a favore di matrici giganti di semplici vecchi tipi di dati. Evitava anche di usare Vector
e Matrix
oggetti, usando solo matrici di float e intervalli di indice. Ovviamente l'implementazione non era così carina e assomigliava molto al vecchio codice C non strutturato, ma era solo per i percorsi e i dati critici di basso livello a cui si accedeva miliardi di volte. La parte di alto livello dell'applicazione era ancora modellata con oggetti. I loro dettagli di implementazione, tuttavia, li hanno evitati.