2015-05-29 13 views
15

Questa domanda riguarda la velocità di di accedere agli elementi di array e slice, non l'efficienza di passarli a funzioni come argomenti.Array vs Slice: velocità di accesso

mi aspetto array essere più veloce di fette nella maggior parte dei casi, perché una fetta è una struttura di dati che descrive una sezione contigua di una matrice e quindi ci può essere un passaggio in più coinvolti quando si accede elementi di una fetta (indirettamente gli elementi della matrice sottostante).

Così ho scritto un piccolo test per confrontare un lotto di operazioni semplici. Ci sono 4 funzioni di riferimento, i primi 2 test di un globale fetta e un allineamento globale, gli altri 2 test di un locale fetta e una matrice locale:

var gs = make([]byte, 1000) // Global slice 
var ga [1000]byte   // Global array 

func BenchmarkSliceGlobal(b *testing.B) { 
    for i := 0; i < b.N; i++ { 
     for j, v := range gs { 
      gs[j]++; gs[j] = gs[j] + v + 10; gs[j] += v 
     } 
    } 
} 

func BenchmarkArrayGlobal(b *testing.B) { 
    for i := 0; i < b.N; i++ { 
     for j, v := range ga { 
      ga[j]++; ga[j] = ga[j] + v + 10; ga[j] += v 
     } 
    } 
} 

func BenchmarkSliceLocal(b *testing.B) { 
    var s = make([]byte, 1000) 
    for i := 0; i < b.N; i++ { 
     for j, v := range s { 
      s[j]++; s[j] = s[j] + v + 10; s[j] += v 
     } 
    } 
} 

func BenchmarkArrayLocal(b *testing.B) { 
    var a [1000]byte 
    for i := 0; i < b.N; i++ { 
     for j, v := range a { 
      a[j]++; a[j] = a[j] + v + 10; a[j] += v 
     } 
    } 
} 

Ho eseguito il test di più volte, ecco l'uscita tipica (go test -bench .*):

BenchmarkSliceGlobal  300000    4210 ns/op 
BenchmarkArrayGlobal  300000    4123 ns/op 
BenchmarkSliceLocal  500000    3090 ns/op 
BenchmarkArrayLocal  500000    3768 ns/op 

Analisi dei risultati:

Accesso al fetta globale è leggermente più lento rispetto l'accesso alla gamma globale che è come mi aspettavo:
4210 vs 4123 ns/OP

Ma di accesso a fetta locale è significativamente più veloce l'accesso alla matrice locale:
3090 vs 3768 ns/OP

La mia domanda è: Qual è la ragione di questo?

Note

Ho provato variando le seguenti cose, ma nessuno ha cambiato il risultato:

  • la dimensione della matrice/slice (provato 100, 1000, 10000)
  • dell'ordine delle funzioni benchmark
  • il tipo di elemento dell'array/slice (provato byte e int)
+0

Ci sono diversi fattori che influenzano tali micro benchmark: controllo dei vincoli, allocazione (heap, stack), generazione di codice, ottimizzazione degli spioncini, ecc. Dai un'occhiata all'output generato dell'assieme in condizioni diverse come il controllo dei vincoli disabilitato o le ottimizzazioni disabilitate. – Volker

+1

È interessante notare che se aggiungi un [puntatore all'array] (http://play.golang.org/p/nyynQgndDl) ai benchmark, vedrai che si comportano all'incirca come slice. –

risposta

11

Confrontando the amd64 assembly sia BenchmarkArrayLocal e BenchmarkSliceLocal (troppo lungo per rientrare in questo post):

La versione matrice carica l'indirizzo di a dalla memoria più volte, praticamente ogni operazione array accesso:

LEAQ "".a+1000(SP),BX 

considerando che la versione fetta sta calcolando esclusivamente sui registri dopo aver caricato una volta dalla memoria:

LEAQ (DX)(SI*1),BX 

Questo non è conclusivo, ma probabilmente la causa. La ragione è che entrambi i metodi sono altrimenti virtualmente identici.Un altro dettaglio notevole è che la versione dell'array chiama in runtime.duffcopy, che è una routine di assemblaggio piuttosto lunga, mentre la versione slice no.

+0

Ma 'runtime.duffcopy()' viene invocato solo una volta a destra? Vedendo che il ciclo di riferimento viene eseguito mezzo milione di volte ('N'), questo non dovrebbe avere alcun effetto sul risultato. – icza

+0

Sì, è quello che penso anch'io. – thwd

+0

Lo stesso risultato in caso di array e slice globali deriva dal fatto che in questo caso il codice compilato non include l'ottimizzazione del "registro" e fa lo stesso caricamento degli indirizzi dalla memoria? – icza

0

Go versione 1.8 può eliminare alcuni controlli di intervallo in modo che la differenza aumenti.

BenchmarkSliceGlobal-4 500000 3220 ns/op BenchmarkArrayGlobal-4 1000000 1287 ns/op BenchmarkSliceLocal-4 1000000 1267 ns/op BenchmarkArrayLocal-4 1000000 1301 ns/op

Per gli array mi consiglia di utilizzare dimensioni da potenze di due e includere un logica e funzionamento. In questo modo sei sicuro che il compilatore elimina il controllo. Quindi var ga [1024]byte con ga[j & 1023].