2011-12-15 9 views
47

È Java System.arraycopy() efficiente per piccoli array o il fatto che si tratti di un metodo nativo rende probabilmente sostanzialmente meno efficiente di un semplice ciclo e una chiamata di funzione?Java System.arraycopy() è efficiente per i piccoli array?

I metodi nativi comportano un sovraccarico aggiuntivo delle prestazioni per attraversare una sorta di bridge di sistema Java?

+10

Hai provato e gestito un punto di riferimento? – corsiKa

+2

Mi piacerebbe vedere un microbenchmark su questo. –

+1

Penso che il codice nativo incorporato non sia influenzato dalle latenze JNI – gd1

risposta

26

Espandendo un po 'quello che Sid ha scritto, è molto probabile che System.arraycopy sia solo un JIT intrinseco; significa che quando il codice chiama System.arraycopy, probabilmente chiamerà un'implementazione specifica JIT (una volta che i tag JIT System.arraycopy sono "caldi") che non viene eseguito tramite l'interfaccia JNI, quindi non comporta il normale overhead di nativo metodi.

In generale, l'esecuzione di metodi nativi ha un sovraccarico (passando attraverso l'interfaccia JNI, anche alcune operazioni JVM interne non possono verificarsi quando i metodi nativi vengono eseguiti). Ma non è perché un metodo è contrassegnato come "nativo" che lo stai effettivamente eseguendo usando JNI. Il JIT può fare cose pazze.

Il modo più semplice per controllare è, come è stato suggerito, scrivendo un piccolo punto di riferimento, facendo attenzione con le normali cautele di microbenchmarks Java (Warm Up il codice prima, evitare di codice con effetti collaterali in quanto il JIT solo ottimizza come un no-op, ecc.).

+0

@ vanze, i piccoli benchmark eseguiti interamente nella cache L1 sono semplicemente sbagliati. – bestsss

+2

@bestsss: Potrebbe aver inteso piccolo in termini di quantità di codice, non necessariamente piccolo in termini di numeri di array su cui farei la copia. – Gravity

+0

@Gravity, la maggior parte delle persone ottiene sempre i micro benchmark errati, specialmente quelli Java. fondamentalmente devi sapere che tipo di codice emette il JIT per scrivere un micro-benchmark corretto. Di solito il modo più semplice è esaminare l'assemblatore generato e confrontare il risultato con quello atteso. – bestsss

5

I codici byte vengono eseguiti in modo nativo in ogni caso, quindi è probabile che le prestazioni siano migliori di un ciclo.

Pertanto, in caso di ciclo, è necessario eseguire codici byte che comportano un sovraccarico. Mentre la copia dell'array dovrebbe essere una memcopia diretta.

+2

C'è un costo aggiuntivo per passare da Java a codice nativo. –

+0

@ AndyThomas-Cramer, non esiste alcun codice nativo in termini di passaggio dal contesto a JNI. E nota a margine: JIT è abbastanza intelligente da ottimizzare la copia dell'array tramite loop sullo stesso codice di 'System.arraycopy' – bestsss

+0

" JIT è abbastanza intelligente da ottimizzare la copia dell'array tramite loop sullo stesso codice di System.arraycopy ". -- È? In teoria questo ha molto senso, ma non ho sentito parlare di una cosa del genere. Puoi indicare un riferimento? – Gravity

-1

Le funzioni native devono essere più veloci delle funzioni JVM, poiché non vi è alcun sovraccarico della VM. Tuttavia, per molti array (> 1000) molto piccoli (len < 10) potrebbe essere più lento.

+0

Puoi per favore elaborare questo? Perché dovrebbe essere più lento per un sacco di piccoli array? –

+0

Sto indovinando a causa della funzione di overhead di chiamata? A meno che anche le funzioni native JIT intrinseche possano essere inline ... – Gravity

+0

Sì, l'overhead della chiamata potrebbe essere maggiore nelle funzioni native. Come si aggiunge, potrebbe superare il tempo risparmiato eliminando l'overhead JVM – laci37

16

Questa è una preoccupazione valida. Ad esempio, nel java.nio.DirectByteBuffer.put(byte[]), l'autore cerca di evitare una copia JNI per piccolo numero di elementi

// These numbers represent the point at which we have empirically 
// determined that the average cost of a JNI call exceeds the expense 
// of an element by element copy. These numbers may change over time. 
static final int JNI_COPY_TO_ARRAY_THRESHOLD = 6; 
static final int JNI_COPY_FROM_ARRAY_THRESHOLD = 6; 

Per System.arraycopy(), siamo in grado di esaminare come JDK lo utilizza. Ad esempio, in ArrayList, viene sempre utilizzato System.arraycopy(), mai "elemento per elemento di copia", indipendentemente dalla lunghezza (anche se è 0). Poiché ArrayList è molto attento alle prestazioni, possiamo dedurre che System.arraycopy() è il modo più efficace di copiatura di array indipendentemente dalla lunghezza.

+11

Suppongo che parte della domanda sia se 'System.arraycopy()' passa per tutto JNI. Come qualcuno ha sottolineato, il semplice fatto che sia dichiarato "nativo" non significa nulla perché la JVM può avere tutti i tipi di ottimizzazioni speciali. – Gravity

+0

Ho pensato esattamente lo stesso pensiero per quanto riguarda la classe 'ArrayList', anche se stavo ponendo questa domanda: che' ArrayList', che si suppone essere una classe ottimizzata, non farebbe nulla di inutilmente costoso. Tuttavia, forse gli autori hanno capito che le prestazioni di piccoli 'ArrayList' non avevano molta importanza (dal momento che sono le operazioni su grandi set di dati che sono comunque più costosi), o forse non è uniformemente efficiente su tutte le JVM. Dovrò fare un punto di riferimento per essere convinto. – Gravity

+2

si preoccuperanno delle prestazioni di * molti * piccoli elenchi di array. quindi ogni piccola lista di array deve funzionare bene. – irreputable

22

Ecco il mio codice di riferimento:

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; 
} 

Dalle mie prove, chiamando test() con copyCount = 10000000 (1E7) o superiore permette il warm-up da realizzare nel corso del primo copy/loop chiamata, in modo da utilizzare testRep = 5 è abbastanza; Con copyCount = 1000000 (1e6) il riscaldamento necessita di almeno 2 o 3 iterazioni in modo da aumentare testRep per ottenere risultati utilizzabili.

Con la mia configurazione (CPU Intel Core 2 Duo E8500 @ 3,16 GHz, Java SE 1.6.0_35-b10 ed Eclipse 3.7.2) risulta dal benchmark che:

  • Quando copySize = 24, System.arraycopy() e il ciclo manuale di prendere quasi lo stesso tempo (a volte uno è leggermente più veloce rispetto agli altri, altre volte è il contrario),
  • Quando copySize < 24, il ciclo manuale è più veloce di System.arraycopy() (leggermente più veloce con copySize = 23, davvero veloce con copySize < 5),
  • Quando copySize> 24, System.arraycopy() è più veloce del ciclo manuale (leggermente più veloce con copySize = 25, il rapporto tempo di ciclo/tempo di copertura della matrice aumenta con l'aumentare di copySize).

Nota: non sono madrelingua inglese, per favore scusate tutti gli errori di grammatica/vocabolario.

+1

Ho ottenuto risultati diversi. Non sembra avere importanza anche con gli array di piccole dimensioni. Grandi array in migliaia, systemarraycopy è molto più veloce. piccoli array non vedo differenze. – RickHigh

+1

Ho avuto anche risultati diversi. Con una dimensione dell'array di 5000 System.arrayCopy era circa 4 volte più veloce e con una dimensione dell'array di soli 5 era ancora circa il 20% più veloce. Sembrava addirittura di 2 dimensioni. Quindi non credo ci sia mai un motivo per non usare System.arrayCopy – nikdeapen

+1

@nikdeapen sembra che la situazione sia cambiata negli anni –

7

utilizzare un'operazione memmove per spostare le parole e l'assieme per lo spostamento di altri tipi primitivi in ​​C dietro la scena. Quindi fa del suo meglio per muoversi tanto quanto può raggiungere.

+1

Si potrebbe desiderare di citare per * quali * implementazioni ... – Pacerier

Problemi correlati