È meglio usare System.arraycopy (...) piuttosto che un ciclo for per copiare gli array?


89

Voglio creare un nuovo array di oggetti mettendo insieme due array più piccoli.

Non possono essere nulli, ma la dimensione può essere 0.

Non posso scegliere tra questi due modi: sono equivalenti o uno è più efficiente (ad esempio system.arraycopy () copia interi blocchi)?

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
System.arraycopy(publicThings, 0, things, 0, publicThings.length);
System.arraycopy(privateThings, 0, things,  publicThings.length, privateThings.length);

o

MyObject[] things = new MyObject[publicThings.length+privateThings.length];
for (int i = 0; i < things.length; i++) {
    if (i<publicThings.length){
        things[i] = publicThings[i]
    } else {
        things[i] = privateThings[i-publicThings.length]        
    }
}

L'unica differenza è l'aspetto del codice?

MODIFICARE: grazie per la domanda collegata, ma sembrano avere una discussione irrisolta:

È davvero più veloce se it is not for native types : byte [], Object [], char []? in tutti gli altri casi viene eseguito un controllo del tipo, che sarebbe il mio caso e quindi sarebbe equivalente ... no?

In un'altra domanda collegata, dicono che the size matters a lot, per la dimensione> 24 vince system.arraycopy (), per meno di 10, il ciclo manuale per è meglio ...

Adesso sono davvero confuso.


16
arraycopy()è una chiamata nativa, che è sicuramente più veloce.
Sotirios Delimanolis

4
Hai provato a confrontare le due diverse implementazioni?
Alex


15
Dovresti scegliere quello che ritieni più leggibile e più facile da mantenere in futuro. Solo quando hai stabilito che questa è l'origine di un collo di bottiglia dovresti cambiare approccio.
arshajii

1
Non reinventare la ruota!
camickr

Risposte:


87
public void testHardCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    for(int i = 0; i < out.length; i++)
    {
        out[i] = bytes[i];
    }
}

public void testArrayCopyBytes()
{
    byte[] bytes = new byte[0x5000000]; /*~83mb buffer*/
    byte[] out = new byte[bytes.length];
    System.arraycopy(bytes, 0, out, 0, out.length);
}

So che i test JUnit non sono davvero i migliori per il benchmarking, ma
testHardCopyBytes ha impiegato 0,157 secondi per essere completato
e
testArrayCopyBytes ha impiegato 0,086 secondi per completare.

Penso che dipenda dalla macchina virtuale, ma sembra che copi blocchi di memoria invece di copiare singoli elementi dell'array. Ciò aumenterebbe assolutamente le prestazioni.

EDIT:
Sembra che le prestazioni di System.arraycopy siano ovunque. Quando vengono utilizzate stringhe al posto dei byte e gli array sono piccoli (dimensione 10), ottengo questi risultati:

    String HC:  60306 ns
    String AC:  4812 ns
    byte HC:    4490 ns
    byte AC:    9945 ns

Ecco come appare quando gli array hanno la dimensione 0x1000000. Sembra che System.arraycopy vinca decisamente con array più grandi.

    Strs HC:  51730575 ns
    Strs AC:  24033154 ns
    Bytes HC: 28521827 ns
    Bytes AC: 5264961 ns

Che strano!

Grazie, Daren, per aver sottolineato che i riferimenti vengono copiati in modo diverso. Ha reso questo un problema molto più interessante!


2
Grazie per il tuo impegno, ma ti sei perso punti apparentemente cruciali: tipi non nativi (crea una classe casuale con anythign in modo che l'array contenga riferimenti) e la dimensione ... sembra per la dimensione dell'array più piccola, il ciclo for manuale è più veloce. Ti interessa correggere questo?
Daren

2
Oh wow, hai ragione! È interessante. Posizionare le stringhe in quegli array invece dei byte fa un'enorme differenza: <<< testHardCopyStrs: 0.161s >>> <<< testArrayCopyStrs: 0.170s >>>
Trent Small

Quali risultati stai ottenendo allora? anche interessante sarebbe provare con array size = 10 ... grazie! (Vorrei avere il mio IDE qui, sto codificando senza compilatore).
Daren

Bene, ora li ho inseriti in alcune chiamate System.nanoTime () e ho impostato size = 10 per vedere quanti nanosecondi richiede ciascuno. Sembra che per piccoli array primitivi, i loop siano migliori; per i riferimenti, arrayCopy è migliore .: <<< testHardCopyBytes: 4491 ns >>> <<< testHardCopyStrs: 56778 ns >>> <<< testArrayCopyBytes: 10265 ns >>> <<< testArrayCopyStrs: 4490 ns >>>
Trent Piccolo

Risultati molto interessanti! grazie mille! puoi modificare la tua risposta per includerla, e la accetterò volentieri in modo che rimanga la prima che tutti vedano ... hai già ottenuto il mio voto. :)
Daren

36

Arrays.copyOf(T[], int)è più facile da leggere. Internaly usa System.arraycopy()che è una chiamata nativa.

Non puoi farlo più velocemente!


sembra che tu possa dipendere da un po 'di cose, ma grazie per aver sottolineato quella funzione che non conoscevo ed è davvero più facile da leggere. :)
Daren

sì! dipende da un bel po 'di cose come ha detto @Svetoslav Tsolov. Volevo solo sottolineare Arrays.copyOf
Philipp Sander

2
copyOfnon può sempre sostituire arraycopy, ma è appropriato per questo caso d'uso.
Blaisorblade

1
NB Se stai osservando le prestazioni, questo non sarà veloce come System.arraycopy () poiché richiede l'allocazione della memoria. Se questo è in un ciclo, le allocazioni ripetute porteranno alla raccolta dei rifiuti che sarà un enorme calo delle prestazioni.
Will Calderwood,

1
@PhilippSander Solo per verificare di non essere stupido ho aggiunto del codice alla copia di un array da 1 MB nel mio loop di gioco, che non attiva quasi mai GC. Con Array.copyOf () il mio DVM chiamava GC 5 volte al secondo e il gioco è diventato estremamente lento. Penso che sia sicuro dire che si sta verificando l'allocazione della memoria.
Will Calderwood

17

Dipende dalla macchina virtuale, ma System.arraycopy dovrebbe darti il ​​massimo che puoi ottenere dalle prestazioni native.

Ho lavorato per 2 anni come sviluppatore Java per sistemi embedded (dove le prestazioni sono una priorità enorme) e ovunque si possa usare System.arraycopy, l'ho usato principalmente / visto usato nel codice esistente. È sempre preferibile rispetto ai loop quando le prestazioni sono un problema. Se le prestazioni non sono un grosso problema, vorrei seguire il ciclo, però. Molto più facile da leggere.


Cose come la dimensione e il tipo di array (di base vs ereditato) sembrano influenzare le prestazioni.
Daren

2
Sì, non è "prestazioni native" di per sé, motivo per cui ho detto che lo uso "principalmente" dove posso (noterai che per lo più vince sulla copia in loop). Immagino che il motivo sia: quando si tratta di un piccolo array di un tipo primitivo, il "costo da chiamare" è maggiore dell'aumento delle prestazioni. L'uso di JNI può degradare le prestazioni per lo stesso motivo: il codice nativo stesso è veloce, ma chiamandolo da un processo java non così tanto.

Correzione minore, Arrays.copy non è JNI, è un elemento intrinseco. Le intrinseche sono molto più veloci di JNI. Come e quando il compilatore JIT lo trasforma in un intrinseco dipende dalla JVM / compilatore utilizzato.
Nitsan Wakart

1
Arrays.copynon esiste, Arrays.copyOfè una funzione di libreria.
Blaisorblade

12

Invece di fare affidamento su speculazioni e possibilmente su informazioni obsolete, ho eseguito alcuni benchmark utilizzando . In effetti, Caliper viene fornito con alcuni esempi, incluso uno CopyArrayBenchmarkche misura esattamente questa domanda! Tutto quello che devi fare è correre

mvn exec:java -Dexec.mainClass=com.google.caliper.runner.CaliperMain -Dexec.args=examples.CopyArrayBenchmark

I miei risultati si basano sulla VM del server Oracle Java HotSpot (TM) a 64 bit, 1.8.0_31-b13, in esecuzione su un MacBook Pro di metà 2010 (macOS 10.11.6 con Intel Arrandale i7, 8 GiB di RAM). Non credo che sia utile pubblicare i dati grezzi sui tempi. Piuttosto, riassumerò le conclusioni con le visualizzazioni di supporto.

In sintesi:

  • Scrivere un forciclo manuale per copiare ogni elemento in un array appena istanziato non è mai vantaggioso, sia per array brevi che per array lunghi.
  • Arrays.copyOf(array, array.length)e array.clone()sono entrambi costantemente veloci. Queste due tecniche sono quasi identiche nelle prestazioni; quale scegli è una questione di gusti.
  • System.arraycopy(src, 0, dest, 0, src.length)è veloce quasi quanto e , ma non in modo abbastanza coerente. (Vedere il caso per 50000 s.) Per questo motivo e per la verbosità della chiamata, consiglierei se hai bisogno di un controllo preciso su quali elementi vengono copiati e dove.Arrays.copyOf(array, array.length)array.clone()intSystem.arraycopy()

Ecco i grafici dei tempi:

Tempistiche per la copia di array di lunghezza 5 Tempistiche per la copia di array di lunghezza 500 Tempistiche per la copia di array di lunghezza 50000


3
C'è qualcosa di strano nella copia int? Sembra strano che la copia arraycopy sia lenta sugli int su larga scala.
whaleberg

6

L'esecuzione di metodi nativi come Arrays.copyOf(T[], int)ha un certo sovraccarico, ma non significa che non sia veloce poiché lo stai eseguendo usando JNI.

Il modo più semplice è scrivere un benchmark e un test.

Puoi verificare che Arrays.copyOf(T[], int)sia più veloce del tuo forciclo normale .

Il codice benchmark da qui : -

public void test(int copySize, int copyCount, int testRep) {
    System.out.println("Copy size = " + copySize);
    System.out.println("Copy count = " + copyCount);
    System.out.println();
    for (int i = testRep; i > 0; --i) {
        copy(copySize, copyCount);
        loop(copySize, copyCount);
    }
    System.out.println();
}

public void copy(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        System.arraycopy(src, 1, dst, 0, copySize);
        dst[copySize] = src[copySize] + 1;
        System.arraycopy(dst, 0, src, 0, copySize);
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Arraycopy: " + (end - begin) / 1e9 + " s");
}

public void loop(int copySize, int copyCount) {
    int[] src = newSrc(copySize + 1);
    int[] dst = new int[copySize + 1];
    long begin = System.nanoTime();
    for (int count = copyCount; count > 0; --count) {
        for (int i = copySize - 1; i >= 0; --i) {
            dst[i] = src[i + 1];
        }
        dst[copySize] = src[copySize] + 1;
        for (int i = copySize - 1; i >= 0; --i) {
            src[i] = dst[i];
        }
        src[copySize] = dst[copySize];
    }
    long end = System.nanoTime();
    System.out.println("Man. loop: " + (end - begin) / 1e9 + " s");
}

public int[] newSrc(int arraySize) {
    int[] src = new int[arraySize];
    for (int i = arraySize - 1; i >= 0; --i) {
        src[i] = i;
    }
    return src;
}

System.arraycopy()utilizza JNI (Java Native Interface) per copiare un array (o parti di esso), quindi è incredibilmente veloce, come puoi confermare qui


Questo codice usa int [] puoi provarlo con String [] (inizializzato con valori diversi: "1", "2", ecc. Poiché sono immutabili
Daren

1
JNI è molto lento . System.arraycopynon lo usa.
Chai T. Rex

No, System.arraycopynon usa JNI, che serve solo per chiamare librerie di terze parti. Invece è una chiamata nativa, il che significa che c'è un'implementazione nativa nella VM per essa.
spheenik

6

Non è possibile che Arrays.copyOfsia più veloce di System.arraycopypoiché questa è l'implementazione di copyOf:

public static int[] copyOf(int[] original, int newLength) {
    int[] copy = new int[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

4

System.arraycopy()è una chiamata nativa che esegue l'operazione di copia direttamente in memoria. Una singola copia della memoria sarebbe sempre più veloce del tuo ciclo for


3
Ho letto che per i tipi non nativi (qualsiasi classe creata, come la mia) potrebbe non essere così efficiente ... e per le dimensioni piccole (il mio caso) il manuale per il ciclo può essere migliore ... ti interessa commentare?
Daren

In effetti, System.arraycopy () ha un po 'di overhead quindi per piccoli array (n = ~ 10) un ciclo è effettivamente più veloce
RecursiveExceptionException
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.