2014-07-20 16 views
15

Perché TypedArrays non è più veloce degli array normali? Voglio usare i valori di precalc per CLZ (calcolare la funzione degli zeri iniziali). E non voglio che interpretino come oggetti soliti?Javascript TypedArray performance

http://jsperf.com/array-access-speed-2/2

codice Preparazione:

Benchmark.prototype.setup = function() { 
    var buffer = new ArrayBuffer(0x10000); 
    var Uint32 = new Uint32Array(buffer); 
    var arr = []; 
    for(var i = 0; i < 0x10000; ++i) { 
    Uint32[i] = (Math.random() * 0x100000000) | 0; 
    arr[i] = Uint32[i]; 
    } 
    var sum = 0; 
}; 

Test 1:

sum = arr[(Math.random() * 0x10000) | 0]; 

Test 2:

sum = Uint32[(Math.random() * 0x10000) | 0]; 

enter image description here

PS Forse i miei test di perf sono invalidi sentiti libero di correggermi.

+0

Hai eliminato i test jsperf? Non riesco più ad accedervi – Bergi

+0

No, non l'ho rimosso. Questo è strano. –

+1

Molto, molto strano che sarebbero scomparsi in quel modo! Tutti loro ... Particolarmente strano alla luce di [questa voce delle FAQ] (http://jsperf.com/faq#test-availability). Ho cercato nel caso in cui l'URL fosse cambiato o qualcosa del genere, e ... niente. Posso trovare altri test sulle prestazioni dell'array, ma non su quelli di Sukhanov e dei miei. Ho sollevato un [problema per questo] (https://github.com/mathiasbynens/jsperf.com/issues/197). Certo, probabilmente non sarebbe male se più di noi [donati a jsPerf] (http://jsperf.com/faq#donate) (e ho appena fatto ... per la prima volta * la testa d'anatra *). –

risposta

27
var buffer = new ArrayBuffer(0x10000); 
var Uint32 = new Uint32Array(buffer); 

non è la stessa cosa:

var Uint32 = new Uint32Array(0x10000); 

non a causa della nuova ArrayBuffer (si ottiene sempre un buffer array: vedi Uint32.buffer in entrambi i casi), ma a causa del parametro di lunghezza: con ArrayBuffer avete 1 byte per elemento, con Uint32Array hai 4 byte per elemento.

Quindi, nel primo caso (e nel codice), Uint32.length = 0x1000/4 e i vostri loop sono fuori limite 3 su 4 volte. Ma purtroppo non otterrai mai errori, solo prestazioni scadenti.

Usando 'nuova ArrayBuffer', si deve dichiarare Uint32 in questo modo:

var buffer = new ArrayBuffer(0x10000 * 4); 
var Uint32 = new Uint32Array(buffer); 

Vedi jsperf with (0x10000) e jsperf with (0x10000 * 4).

+3

È un peccato che le persone accettino risposte basate sulla dimensione del contenuto, ma non sulla correttezza. – MorrisLiang

+1

@MorrisLiang, è quello che pensavo anch'io, ma poi ho capito che questa risposta era stata postata più di sei mesi dopo la domanda. – user2033427

+0

@MorrisLiang: Si noti che la mia risposta * sempre * ha usato 'nuovo Uint32Array (0x10000)' per confrontare le mele con le mele in base alla lunghezza e ha rivelato ancora differenze di velocità. Ma non ho fatto notare la differenza nel modo in cui Radsoc ha fatto sopra. –

25

I motori moderni useranno i veri array dietro le quinte anche se si pensa di poterlo fare, ricadendo su "array" di mappe di proprietà se si fa qualcosa che gli fa pensare di non poter usare un vero array.

noti inoltre che come radsoc points out, var buffer = new ArrayBuffer(0x10000) quindi var Uint32 = new Uint32Array(buffer) produce una matrice Uint32 cui dimensione è 0x4000 (0x10000/4), non 0x10000, perché il valore si dà ArrayBuffer è in byte, ma naturalmente ci sono quattro byte per ogni Uint32Array . Tutti i seguenti usi new Uint32Array(0x10000) invece (e sempre fatto, anche prima di questa modifica) per confrontare le mele con le mele.

Così iniziamo lì, con new Uint32Array(0x10000): http://jsperf.com/array-access-speed-2/11(purtroppo, JSPerf ha perso questo test e dei suoi risultati, e 'ora offline del tutto)

graph showing roughly equivalent performance

Ciò suggerisce che perché si' riempiendo l'array in un modo semplice e prevedibile, un motore moderno continua a utilizzare un vero array (con i relativi vantaggi in termini di prestazioni) sotto le coperte anziché spostarsi. Vediamo sostanzialmente le stesse prestazioni per entrambi. La differenza di velocità potrebbe riguardare la conversione del tipo prendendo il valore Uint32 e assegnandolo a sum come number (anche se sono sorpreso se quella conversione di tipo non viene differita ...).

Aggiungere un po 'di caos, però:

var Uint32 = new Uint32Array(0x10000); 
var arr = []; 
for (var i = 0x10000 - 1; i >= 0; --i) { 
    Uint32[Math.random() * 0x10000 | 0] = (Math.random() * 0x100000000) | 0; 
    arr[Math.random() * 0x10000 | 0] = (Math.random() * 0x100000000) | 0; 
} 
var sum = 0; 

... in modo che il motore deve ripiegare su vecchio stile mappa proprietà "array", e si vede che gli array tipizzati marcatamente sovraperformare il vecchio stile genere: http://jsperf.com/array-access-speed-2/3(purtroppo, ha perso JSPerf questo test e dei suoi risultati)

bar graph showing marked performance improvement for typed arrays

Clever, questi JavaSc ingegneri del motore ripuliti ...

La cosa specifica che si esegue con la natura non di matrice dell'array Array è importante; prendere in considerazione:

var Uint32 = new Uint32Array(0x10000); 
var arr = []; 
arr.foo = "bar";       // <== Non-element property 
for (var i = 0; i < 0x10000; ++i) { 
    Uint32[i] = (Math.random() * 0x100000000) | 0; 
    arr[i] = (Math.random() * 0x100000000) | 0; 
} 
var sum = 0; 

che è ancora riempiendo la matrice prevedibile, ma aggiungiamo una struttura non-elemento (foo) ad esso. http://jsperf.com/array-access-speed-2/4(purtroppo, JSPerf ha perso questo test e dei suoi risultati) A quanto pare, i motori sono abbastanza intelligente, e mantenere la proprietà non-elemento a lato, pur continuando a utilizzare un vero array per le proprietà degli elementi:

bar graph showing performance improvement for standard arrays when <code>Array</code> array gets non-element property

sono a un po 'di una perdita per spiegare il motivo per cui le matrici normali dovrebbero ottenere più velocemente lì rispetto alla nostra prima prova di cui sopra. Errore di misurazione? Vagaries in Math.random? Ma siamo ancora abbastanza sicuri che i dati specifici dell'array nello Array siano ancora un array vero.

Mentre se facciamo la stessa cosa ma compilare in ordine inverso:

var Uint32 = new Uint32Array(0x10000); 
var arr = []; 
arr.foo = "bar";       // <== Non-element property 
for (var i = 0x10000 - 1; i >= 0; --i) { // <== Reverse order 
    Uint32[i] = (Math.random() * 0x100000000) | 0; 
    arr[i] = (Math.random() * 0x100000000) | 0; 
} 
var sum = 0; 

... noi tornare a vincere array tipizzati fuori   — eccetto IE11: http://jsperf.com/array-access-speed-2/9(purtroppo, ha JSPerf perso questo test e dei suoi risultati)

graph showing typed arrays winning except on IE11

+2

Interessante, grazie. Le cose diventano più complicate in caso di uso reale, in cui l'allocazione/disallocazione continua di vari oggetti/matrici porta alla frammentazione della memoria e il tempo di elaborazione esplode se la memoria non è pre-allocata. Nella vita reale, Typed Array costerà meno poiché sono pre-allocati (Rq: js Array potrebbe essere anche preallocato ...) – GameAlchemist

+0

bella spiegazione, grazie per questo. – qodeninja

+0

La scrittura di valori int32 precalcolati su array digitato è veloce. L'array non contiene buchi (sì, i buchi sono veloci ma ...). L'array è preallocato. Quindi la creazione e l'inizializzazione dell'array digitato è veloce. Ma quando provi a leggere da Uint32Array il valore dovrebbe essere convertito in double (vedi la mia risposta sotto). È necessario utilizzare Int32Array (su x64) o Int16Array (su x86) (vedere di nuovo la mia risposta di seguito). – vitaliydev

0

Nel tuo caso il motivo delle cattive prestazioni è il tentativo di leggere all'esterno dell'array quando si utilizza Uint32Array a causa di un bug con lunghezza dell'array.

Ma se non sarebbe il vero motivo poi:

Provare a usare Int32Array invece di Uint32Array. Penso che nelle variabili V8 non possa avere il tipo uint32 ma possa avere int32/double/pointer. Quindi quando assegni il tipo uint32 alla variabile sarà convertito in doppio più lento.

Se si utilizza la versione V8 a 32 bit, le variabili possono avere il tipo int31/doppio/puntatore. Quindi int32 verrà convertito in doppio. Ma se si usa la solita matrice e tutti i valori sono int31, la conversione non è necessaria, quindi la matrice usuale può essere più veloce.

Anche l'uso di int16 può richiedere alcune conversioni per ottenere int32 (a causa del segno e del complemento di un). Uint16 non richiede la conversione perché V8 può solo aggiungere zeri a sinistra.

PS. Potrebbero essere interessati i puntatori e int31 (o int32 su x64) sono le stesse cose in V8. Ciò significa anche che int32 richiederà 8 byte su x64. Anche questo è il motivo per cui non c'è un tipo int32 su x86: perché se usassimo tutti i 32 bit per la memorizzazione di interi non avremmo più spazio per salvare i puntatori.