Perché System.arraycopy è nativo in Java?


85

Sono stato sorpreso di vedere nel codice sorgente Java che System.arraycopy è un metodo nativo.

Ovviamente il motivo è perché è più veloce. Ma quali trucchi nativi è in grado di utilizzare il codice per renderlo più veloce?

Perché non eseguire il ciclo sull'array originale e copiare ogni puntatore nel nuovo array - sicuramente questo non è così lento e macchinoso?

Risposte:


83

Nel codice nativo, può essere eseguito con una singola memcpy/ memmove, invece di n distinte operazioni di copia. La differenza di prestazioni è sostanziale.


8
In realtà, solo alcune sottocasi di arraycopypotrebbero essere implementate usando memcpy/ memmove. Altri richiedono un controllo del tipo di runtime per ogni elemento copiato.
Stephen C

1
@Stephen C, interessante - perché?
Péter Török

3
@ Péter Török - considera la possibilità di copiare da un Object[]popolato di Stringoggetti a un file String[]. Vedi l'ultimo paragrafo di java.sun.com/javase/6/docs/api/java/lang/…
Stephen C

3
Peter, Object [] e byte [] + char [] sono quelli copiati più spesso, nessuno di loro richiede un controllo del tipo esplicito. Il compilatore è abbastanza intelligente da NON controllare se non necessario e praticamente nel 99,9% dei casi non lo è. La parte divertente è che le copie di piccole dimensioni (meno di una riga della cache) sono abbastanza dominanti, quindi "memcpy" per cose di piccole dimensioni è veramente importante.
bestsss

1
@jainilvachhani sia memcpye memmovesono O (n), tuttavia perché delle ottimizzazioni di fe simd sono few timespiù veloci, quindi potresti dire che sono O (n / x), dove x dipende dalle ottimizzazioni utilizzate in queste funzioni
ufoq

16

Non può essere scritto in Java. Il codice nativo è in grado di ignorare o elidere la differenza tra array di Object e array di primitive. Java non può farlo, almeno non in modo efficiente.

E non può essere scritto con un singolo memcpy(), a causa della semantica richiesta dalla sovrapposizione di array.


5
Bene, memmoveallora. Anche se non credo che faccia molta differenza nel contesto di questa domanda.
Péter Török

Nemmeno memmove (), vedi i commenti di @Stephen C su un'altra risposta.
user207421

L'ho già visto, visto che era la mia risposta ;-) Ma grazie comunque.
Péter Török

1
@Geek Array che si sovrappongono. Se gli array di origine e di destinazione e gli stessi e solo gli offset sono diversi, il comportamento viene specificato con cura e memcpy () non è conforme.
user207421

1
Non si può scrivere in Java? Non si potrebbe scrivere un metodo generico per gestire le sottoclassi di Object e poi uno per ciascuno dei tipi primitivi?
Michael Dorst

11

Ovviamente dipende dall'implementazione.

HotSpot lo tratterà come un "intrinseco" e inserirà il codice nel sito della chiamata. Questo è il codice macchina, non il vecchio codice C. lento. Ciò significa anche che i problemi con la firma del metodo in gran parte scompaiono.

Un semplice ciclo di copia è abbastanza semplice da poter essere applicato a ovvie ottimizzazioni. Ad esempio lo svolgimento del ciclo. Ciò che accade esattamente dipende ancora dall'implementazione.


2
questa è una risposta molto decente :), esp. la menzione degli intrinseci. senza la loro semplice iterazione potrebbe essere più veloce poiché di solito viene svolto comunque dal JIT
bestsss

4

Nei miei test System.arraycopy () per la copia di array a più dimensioni è da 10 a 20 volte più veloce dell'interleaving per i loop:

float[][] foo = mLoadMillionsOfPoints(); // result is a float[1200000][9]
float[][] fooCpy = new float[foo.length][foo[0].length];
long lTime = System.currentTimeMillis();
System.arraycopy(foo, 0, fooCpy, 0, foo.length);
System.out.println("native duration: " + (System.currentTimeMillis() - lTime) + " ms");
lTime = System.currentTimeMillis();

for (int i = 0; i < foo.length; i++)
{
    for (int j = 0; j < foo[0].length; j++)
    {
        fooCpy[i][j] = foo[i][j];
    }
}
System.out.println("System.arraycopy() duration: " + (System.currentTimeMillis() - lTime) + " ms");
for (int i = 0; i < foo.length; i++)
{
    for (int j = 0; j < foo[0].length; j++)
    {
        if (fooCpy[i][j] != foo[i][j])
        {
            System.err.println("ERROR at " + i + ", " + j);
        }
    }
}

Questo stampa:

System.arraycopy() duration: 1 ms
loop duration: 16 ms

10
Anche se questa domanda è vecchia, solo per la cronaca: NON è un benchmark equo (per non parlare della domanda se un tale benchmark avrebbe senso in primo luogo). System.arraycopyesegue una copia superficiale ( vengono copiati solo i riferimenti alle float[]s interne ), mentre i forcicli nidificati eseguono una copia profonda ( floatby float). Una modifica a fooCpy[i][j]si rifletterà foonell'utilizzo System.arraycopy, ma non utilizzerà i forloop nidificati .
misberner

4

Ci sono alcuni motivi:

  1. È improbabile che JIT generi un codice di basso livello efficiente come un codice C scritto manualmente. L'utilizzo di C di basso livello può consentire molte ottimizzazioni che sono quasi impossibili da eseguire per un compilatore JIT generico.

    Vedi questo collegamento per alcuni trucchi e confronti di velocità di implementazioni C scritte a mano (memcpy, ma il principio è lo stesso): Controlla questo Ottimizzare Memcpy migliora la velocità

  2. La versione C è praticamente indipendente dal tipo e dalla dimensione dei membri dell'array. Non è possibile fare lo stesso in java poiché non c'è modo di ottenere il contenuto dell'array come un blocco di memoria grezzo (es. Puntatore).


1
Il codice Java può essere ottimizzato. In effetti, ciò che effettivamente accade è che viene generato il codice macchina che è più efficiente del C.
Tom Hawtin - tackline

Sono d'accordo che a volte il codice JITed sarà ottimizzato meglio localmente poiché sa su quale processore è in esecuzione. Tuttavia, poiché è "just in time", non sarà mai in grado di utilizzare tutte quelle ottimizzazioni non locali che richiedono più tempo per essere eseguite. Inoltre, non sarà mai in grado di abbinare il codice C realizzato a mano (che potrebbe anche tenere in considerazione il processore e negare parzialmente i vantaggi JIT, compilando per un processore specifico o tramite qualche tipo di controllo di runtime).
Hrvoje Prgeša

1
Penso che il team del compilatore Sun JIT contesterebbe molti di questi punti. Ad esempio, credo che HotSpot esegua l'ottimizzazione globale per rimuovere l'invio di metodi non necessari e non c'è motivo per cui un JIT non possa generare codice specifico del processore. Poi c'è il punto che un compilatore JIT può eseguire l'ottimizzazione del ramo in base al comportamento di esecuzione dell'applicazione corrente eseguita.
Stephen C

@Stephen C - eccellente punto sulle ottimizzazioni dei rami, anche se potresti anche eseguire il profilo statico delle prestazioni con i compilatori C / C ++ per ottenere l'effetto simile. Penso anche che l'hotspot abbia 2 modalità di funzionamento: le applicazioni desktop non utilizzeranno tutte le ottimizzazioni disponibili per ottenere un tempo di avvio ragionevole, mentre le applicazioni server saranno ottimizzate in modo più aggressivo. Tutto sommato, ottieni alcuni vantaggi, ma ne perdi anche alcuni.
Hrvoje Prgeša

1
System.arrayCopy non è implementato utilizzando C, il che in qualche modo invalida questa risposta
Nitsan Wakart
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.