Pourquoi arr = [] est-il plus rapide que arr = new Array?

146

J'ai exécuté ce code et j'ai obtenu le résultat ci-dessous. Je suis curieux de savoir pourquoi []est plus rapide?

console.time('using[]')
for(var i=0; i<200000; i++){var arr = []};
console.timeEnd('using[]')

console.time('using new')
for(var i=0; i<200000; i++){var arr = new Array};
console.timeEnd('using new')
  • en utilisant []: 299ms
  • en utilisant new: 363ms

Grâce à Raynos, voici un benchmark de ce code et une autre manière possible de définir une variable.

entrez la description de l'image ici

Mohsen
la source
5
Vous pourriez être intéressé par jsperf .
Pointy du
11
Benchmark
Raynos
Notez le mot-clé nouveau. Cela signifie "s'il vous plaît soyez moins efficace". Cela n'a jamais de sens et oblige le navigateur à faire l'instanciation normale au lieu d'essayer de faire des optimisations.
beatgammit
2
@kinakuta no. Ils créent tous les deux de nouveaux objets non égaux. Je voulais []dire équivaut à new Array()en termes de code source, pas d'objets renvoyés sous forme d'expressions
Raynos
1
Oui, ce n'est pas très important. Mais j'aime savoir.
Mohsen

Réponses:

195

Élargir davantage les réponses précédentes ...

Du point de vue général des compilateurs et sans tenir compte des optimisations spécifiques aux VM:

Tout d'abord, nous passons par la phase d'analyse lexicale où nous tokenisons le code.

A titre d'exemple, les jetons suivants peuvent être produits:

[]: ARRAY_INIT
[1]: ARRAY_INIT (NUMBER)
[1, foo]: ARRAY_INIT (NUMBER, IDENTIFIER)
new Array: NEW, IDENTIFIER
new Array(): NEW, IDENTIFIER, CALL
new Array(5): NEW, IDENTIFIER, CALL (NUMBER)
new Array(5,4): NEW, IDENTIFIER, CALL (NUMBER, NUMBER)
new Array(5, foo): NEW, IDENTIFIER, CALL (NUMBER, IDENTIFIER)

J'espère que cela devrait vous fournir une visualisation suffisante pour que vous puissiez comprendre combien de traitement est nécessaire.

  1. Sur la base des jetons ci-dessus, nous savons que ARRAY_INIT produira toujours un tableau. Nous créons donc simplement un tableau et le peuplons. En ce qui concerne l'ambiguïté, l'étape d'analyse lexicale a déjà distingué ARRAY_INIT d'un accesseur de propriété d'objet (par exemple obj[foo]) ou de crochets à l'intérieur de chaînes / littéraux regex (par exemple "foo [] bar" ou / [] /)

  2. C'est minuscule, mais nous avons aussi plus de jetons avec new Array. De plus, il n'est pas encore tout à fait clair que nous souhaitons simplement créer un tableau. On voit le «nouveau» jeton, mais «nouveau» quoi? Nous voyons alors le jeton IDENTIFIER qui signifie que nous voulons un nouveau «tableau», mais les VM JavaScript ne distinguent généralement pas un jeton IDENTIFIER et des jetons pour les «objets globaux natifs». Par conséquent...

  3. Nous devons rechercher la chaîne de portée chaque fois que nous rencontrons un jeton IDENTIFIER. Les VM Javascript contiennent un "objet Activation" pour chaque contexte d'exécution qui peut contenir l'objet "arguments", des variables définies localement, etc. Si nous ne pouvons pas le trouver dans l'objet Activation, nous commençons à rechercher la chaîne de portée jusqu'à ce que nous atteignions la portée globale . Si rien n'est trouvé, nous lançons un fichier ReferenceError.

  4. Une fois que nous avons localisé la déclaration de variable, nous appelons le constructeur. new Arrayest un appel de fonction implicite, et la règle de base est que les appels de fonction sont plus lents pendant l'exécution (d'où la raison pour laquelle les compilateurs statiques C / C ++ autorisent la "fonction en ligne" - ce que les moteurs JS JIT tels que SpiderMonkey doivent faire à la volée)

  5. Le Arrayconstructeur est surchargé. Le constructeur Array est implémenté en tant que code natif, il fournit donc des améliorations de performances, mais il doit toujours vérifier la longueur des arguments et agir en conséquence. De plus, dans le cas où un seul argument est fourni, nous devons vérifier davantage le type de l'argument. new Array ("foo") produit ["foo"] où comme new Array (1) produit [undefined]

Donc, pour simplifier tout cela: avec les littéraux de tableau, la VM sait que nous voulons un tableau; avec new Array, la machine virtuelle doit utiliser des cycles CPU supplémentaires pour déterminer ce que new Array fait réellement .

Roger Poon
la source
n'est pas a = new Array (1000); for (de 0 à 999) {a [i] = i} plus rapide que a = []; for (de 0 à 999) {a [i] = i} à cause de les frais généraux d'allocation?
Y. Yoshii
Je viens de faire un cas de test. new Array (n) est plus rapide dans les cas où vous connaissez la taille du tableau à l'avance jsperf.com/square-braces-vs-new-array
Y. Yoshii
27

Une raison possible est que cela new Arraynécessite une recherche de nom Array(vous pouvez avoir une variable avec ce nom dans la portée), alors que ce []n'est pas le cas.

Hammar
la source
4
La vérification des arguments peut également y contribuer.
Leonid
Arrayexclut à la fois un argument lenet plusieurs arguments. Où as []n'accepte que plusieurs arguments. De plus, les tests de Firefox ne montrent presque aucune différence.
Raynos
Je pense qu'il y a du vrai là-dedans. L'exécution du test de boucle d'OP dans un IIFE a un impact (relativement) substantiel sur les performances. Y compris var Array = window.Arrayaméliore les performances du new Arraytest.
user113716
Je ne pense pas que ce soit juste parce que cette console.time ('more vars new'); pour (var i = 0; i <200000; i ++) {var arr = new Array ()}; console.timeEnd ('more vars new'); plus de vars new: 390ms et cette console.time ('more vars new'); var myOtherObject = {}, myOtherArray = []; pour (var i = 0; i <200000; i ++) {var arr = new Array ()}; console.timeEnd ('more vars new'); plus de vars nouveau: 369ms Renvoie en même temps
Mohsen
2

Bonne question. Le premier exemple est appelé un littéral de tableau. C'est le moyen préféré de créer des tableaux parmi de nombreux développeurs. Il se peut que la différence de performances soit causée par la vérification des arguments du nouvel appel Array () puis la création de l'objet, tandis que le littéral crée directement un tableau.

Je pense que la différence de performances relativement faible confirme ce point. Vous pouvez faire le même test avec le littéral Object et object {} d'ailleurs.

Laurent Zuijdwijk
la source
1

Cela aurait du sens

Les objets littéraux nous permettent d'écrire du code qui prend en charge de nombreuses fonctionnalités tout en le rendant relativement simple pour les implémenteurs de notre code. Pas besoin d'appeler directement les constructeurs ou de maintenir l'ordre correct des arguments passés aux fonctions, etc.

http://www.dyn-web.com/tutorials/obj_lit.php

lnguyen55
la source
1

Aussi, intéressant, si la longueur du tableau est connue à l'avance (les éléments seront ajoutés juste après la création), l'utilisation d'un constructeur de tableau avec une longueur spécifiée est beaucoup plus rapide sur les récents Google Chrome 70+.

  • " new Array ( % ARR_LENGTH% ) " - 100% (plus rapide) !

  • " [] " - 160-170% (plus lent)

Graphique avec les résultats des mesures.

Le test peut être trouvé ici - https://jsperf.com/small-arr-init-with-known-length-brackets-vs-new-array/2

Remarque: ce résultat testé sur Google Chrome v.70 + ; dans Firefox v.70 et IE, les deux variantes sont presque égales.

Oleg Zarevennyi
la source