La lettura della proprietà `length` di un array è davvero un’operazione costosa in JavaScript?

Ho sempre pensato che il caching della lunghezza di un array in JavaScript fosse una buona idea (specialmente nella condizione di un ciclo for ) a causa dell’economicità del calcolo della lunghezza di un array.

Esempio

 for (var i = 0; i < arr.length; i++) { } // vs for (var i = 0, arrLength = arr.length; i < arrLength; i++) { } 

Tuttavia, ho pensato che forse la proprietà length è stata aggiornata solo sulla creazione e l’alterazione dell’array. Pertanto, la lettura non dovrebbe essere un’operazione troppo costosa rispetto alla lettura memorizzata in una variabile (al contrario di altri metodi in altre lingue che potrebbero dover cercare in memoria per trovare la fine di qualcosa, ad esempio strlen() in C ).

Ho due domande. Sono anche interessato a come funziona, quindi per favore non colpirmi con la chiavetta di ottimizzazione prematura .

Assumi i motori JavaScript nei browser.

  1. C’è qualche vantaggio nel caching della proprietà length di un array in JavaScript? C’è molto di più nella lettura di una variabile locale sulla proprietà di un object?
  2. La proprietà length semplicemente alterata nei metodi di creazione e nei metodi shift() e pop() che non restituiscono un nuovo array e vengono semplicemente archiviati come interi?

Beh, avrei detto che era costoso, ma poi ho scritto un piccolo test @ jsperf.com e con mia sorpresa usando i realtà era più veloce in Chrome, e in FF (4) non aveva importanza.

Il mio sospetto è che la lunghezza sia memorizzata come un intero (Uint32). Dalle specifiche ECMA (262 ed. 5, pagina 121):

Ogni object Array ha una proprietà length il cui valore è sempre un numero intero non negativo inferiore a 2 32 . Il valore della proprietà length è numericamente maggiore del nome di ogni proprietà il cui nome è un indice array; ogni volta che una proprietà di un object Array viene creata o modificata, altre proprietà vengono modificate in base alle necessità per mantenere invariata questa proprietà. In particolare, ogni volta che viene aggiunta una proprietà il cui nome è un indice di matrice, la proprietà length viene modificata, se necessario, per essere un valore in più rispetto al valore numerico di tale indice di array; e ogni volta che viene modificata la proprietà length, ogni proprietà il cui nome è un indice array il cui valore non è inferiore alla nuova lunghezza viene automaticamente cancellato. Questo vincolo si applica solo alle proprietà proprie di un object Array e non è influenzato dalla lunghezza o dalle proprietà dell'indice dell'array che possono essere ereditate dai suoi prototipi

Accidenti! Non so se mi abituerò mai a un tale linguaggio ...

Infine, abbiamo sempre il nostro buon vecchio in ritardo rispetto al browser. In IE (9, 8, 7) il caching della lunghezza è molto più veloce. Uno dei tanti altri motivi per non usare IE, dico.

TL; DR:

Da quello che posso raccogliere, sembra che la lunghezza dell’array sia memorizzata nella cache internamente (almeno in V8).

(Dettagli? Continua a leggere :))

Quindi, questa domanda mi è venuta in mente un paio di volte e ho deciso di arrivare alla radice del problema (almeno in un’implementazione).

Scavando attorno all’origine V8 ha prodotto la class JSArray .

 // The JSArray describes JavaScript Arrays // Such an array can be in one of two modes: // - fast, backing storage is a FixedArray and length <= elements.length(); // Please note: push and pop can be used to grow and shrink the array. // - slow, backing storage is a HashTable with numbers as keys. 

Suppongo che il tipo di elementi dell'array stabilisca se è veloce o lento. Ho ottenuto una bit flag impostata in set_has_fast_elements ( set_bit_field2(bit_field2() | (1 << kHasFastElements)) ), che è dove ho pensato di disegnare la linea di scavo come cercavo nel codice google e non avere la fonte localmente

Ora, sembra che ogni volta che un'operazione viene eseguita sull'array (che è una class figlio di JSObject , viene effettuata una chiamata a NormalizeElements() , che esegue quanto segue:

 // Compute the effective length. int length = IsJSArray() ? Smi::cast(JSArray::cast(this)->length())->value() : array->length(); 

Quindi, nel rispondere alle tue domande:

  1. Non sembra esserci alcun vantaggio in Chrome (o altri browser che usano V8) per memorizzare nella cache la proprietà length di un array (a meno che non si stia facendo qualcosa di strano che lo costringerebbe ad essere slow (non sono sicuro di cosa siano quelli condizioni sono) - detto questo, molto probabilmente continuerò a memorizzare la length finché non avrò la possibilità di passare attraverso tutte le implementazioni del browser OS;)
  2. La proprietà length sembra essere modificata dopo ogni operazione sull'object.

Modificare:

In una nota a margine, sembra che un array "vuoto" sia effettivamente assegnato per avere 4 elementi:

 // Number of element slots to pre-allocate for an empty array. static const int kPreallocatedArrayElements = 4; 

Non sono sicuro di quanti elementi l'array cresce una volta che i limiti sono stati superati - non ho scavato così in profondità 🙂

Un’altra serie di test delle prestazioni. Il ciclo viene eseguito su una serie di milioni di numeri casuali con un ciclo vuoto.

In Chrome, i cicli con lunghezze memorizzate nella cache e senza cache hanno più o meno gli stessi orari, quindi suppongo che si tratti di un’ottimizzazione V8 per archiviare la lunghezza.

In Safari e Firefox, la lunghezza della cache era costantemente circa 2 volte più veloce rispetto alla versione non memorizzata nella cache.

Solo una nota:

Su alcuni browser (l’ho notato in Safari, IE e Opera), è ansible ottenere un aumento della velocità memorizzando nella cache la lunghezza all’interno della dichiarazione del ciclo for:

 var j; for (var i = 0, len = arr.length; i < len; i++) { j = arr[i]; } 

Ho modificato il test jsperf di @ KooiInc sopra per aggiungere questo caso .

Questo articolo analizza il caching automatico in V8 e Chrome, chiedendo a IRHydra il codice generato:

Come il Grinch ha rubato l’accesso dell’array.length di Vyacheslav Egorov

Ha scoperto che, in determinate condizioni, memorizzava manualmente nella cache la .length effettiva effettivamente aggiunta piuttosto che migliorare le prestazioni!

Tuttavia, è improbabile che questo tipo di micro-ottimizzazione consegua un guadagno evidente per i tuoi utenti. Per il loro beneficio, e per il vostro, concentratevi invece su un codice che è chiaro da leggere e utilizzando buone strutture dati e algoritmi nel vostro codice!

Evita l’ottimizzazione prematura : concentrati su un codice elegante finché non si verifica un problema di prestazioni. Solo allora, cerca il collo di bottiglia attraverso la profilazione, e poi ottimizza solo quella parte del codice.

Fai attenzione a non presumere che questo è vero per tutte le collezioni iterabili. Ad esempio il caching della lunghezza di un HTMLCollection è del 65% più veloce in Chrome (versione 41) e del 35% più veloce in Firefox (versione 36).

http://jsperf.com/array-length-in-loop-dom