Warum ist arr = [] schneller als arr = new Array?

146

Ich habe diesen Code ausgeführt und das folgende Ergebnis erhalten. Ich bin gespannt, warum []es schneller geht.

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')
  • mit []: 299ms
  • mit new: 363ms

Dank Raynos gibt es hier einen Benchmark für diesen Code und eine weitere Möglichkeit, eine Variable zu definieren.

Geben Sie hier die Bildbeschreibung ein

Mohsen
quelle
5
Sie könnten an jsperf interessiert sein .
Pointy
11
Benchmark
Raynos
Beachten Sie das Schlüsselwort neu. Dies bedeutet "bitte weniger effizient sein". Es macht nie Sinn und erfordert, dass der Browser die normale Instanziierung durchführt, anstatt zu versuchen, Optimierungen vorzunehmen.
Beatgammit
2
@kinakuta nein. Beide erstellen neue ungleiche Objekte. Ich meinte, []ist new Array()in Bezug auf den Quellcode gleichwertig , nicht Objekte, die aus Ausdrücken zurückgegeben werden
Raynos
1
Ja, das ist nicht sehr wichtig. Aber ich möchte es wissen.
Mohsen

Antworten:

195

Weitere Antworten auf frühere Antworten ...

Aus der Sicht eines allgemeinen Compilers und ohne Berücksichtigung von VM-spezifischen Optimierungen:

Zuerst durchlaufen wir die lexikalische Analysephase, in der wir den Code tokenisieren.

Beispielsweise können die folgenden Token hergestellt werden:

[]: 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)

Hoffentlich sollte dies Ihnen eine ausreichende Visualisierung bieten, damit Sie verstehen, wie viel mehr (oder weniger) Verarbeitung erforderlich ist.

  1. Basierend auf den oben genannten Token wissen wir, dass ARRAY_INIT immer ein Array erzeugt. Wir erstellen daher einfach ein Array und füllen es aus. In Bezug auf die Mehrdeutigkeit hat die lexikalische Analysephase ARRAY_INIT bereits von einem Objekteigenschafts-Accessor (z. B. obj[foo]) oder Klammern in Zeichenfolgen / Regex-Literalen (z. B. "foo [] bar" oder / [] /) unterschieden.

  2. Das ist winzig, aber wir haben auch mehr Token mit new Array. Darüber hinaus ist noch nicht ganz klar, dass wir einfach ein Array erstellen wollen. Wir sehen den "neuen" Token, aber "neu" was? Wir sehen dann das IDENTIFIER-Token, das bedeutet, dass wir ein neues "Array" wollen, aber JavaScript-VMs unterscheiden im Allgemeinen kein IDENTIFIER-Token und keine Token für "native globale Objekte". Deshalb...

  3. Wir müssen die Scope-Kette jedes Mal nachschlagen, wenn wir auf ein IDENTIFIER-Token stoßen. Javascript-VMs enthalten für jeden Ausführungskontext ein "Aktivierungsobjekt", das das Objekt "Argumente", lokal definierte Variablen usw. enthalten kann. Wenn wir es im Aktivierungsobjekt nicht finden können, suchen wir die Bereichskette, bis wir den globalen Bereich erreichen . Wenn nichts gefunden wird, werfen wir a ReferenceError.

  4. Sobald wir die Variablendeklaration gefunden haben, rufen wir den Konstruktor auf. new Arrayist ein impliziter Funktionsaufruf, und die Faustregel lautet, dass Funktionsaufrufe während der Ausführung langsamer sind (daher erlauben statische C / C ++ - Compiler "Function Inlining" - was JS JIT-Engines wie SpiderMonkey im laufenden Betrieb tun müssen).

  5. Der ArrayKonstruktor ist überladen. Der Array-Konstruktor ist als nativer Code implementiert, bietet also einige Leistungsverbesserungen, muss jedoch noch die Länge der Argumente überprüfen und entsprechend handeln. Darüber hinaus müssen wir den Typ des Arguments weiter überprüfen, falls nur ein Argument angegeben wird. neues Array ("foo") erzeugt ["foo"], während neues Array (1) [undefiniert] erzeugt

Um alles zu vereinfachen: Mit Array-Literalen weiß die VM, dass wir ein Array wollen. Mit new Arraymuss die VM zusätzliche CPU-Zyklen verwenden, um herauszufinden, was new Array tatsächlich funktioniert.

Roger Poon
quelle
ist nicht a = neues Array (1000); für (von 0 bis 999) {a [i] = i} schneller als a = []; für (von 0 bis 999) {a [i] = i} wegen Zuweisungsaufwand?
Y. Yoshii
Habe gerade einen Testfall gemacht. neues Array (n) ist schneller in Fällen, in denen Sie die Größe des Arrays im Voraus kennen jsperf.com/square-braces-vs-new-array
Y. Yoshii
27

Ein möglicher Grund ist, new Arraydass eine Namenssuche erforderlich ist Array(Sie können eine Variable mit diesem Namen im Gültigkeitsbereich haben), während []dies nicht der Fall ist.

Hammar
quelle
4
Das Überprüfen von Argumenten kann ebenfalls dazu beitragen.
Leonid
ArrayAusgenommen sind sowohl ein Argument lenals auch mehrere Argumente. Wobei as []nur mehrere Argumente akzeptiert. Auch Firefox-Tests zeigen fast keinen Unterschied.
Raynos
Ich denke, das stimmt. Das Ausführen des OP-Schleifentests in einem IIFE hat einen (relativ) erheblichen Einfluss auf die Leistung. Einschließlich var Array = window.Arrayverbessert die Leistung des new ArrayTests.
user113716
Ich denke nicht, dass das richtig ist, weil diese console.time ('more vars new'); für (var i = 0; i <200000; i ++) {var arr = new Array ()}; console.timeEnd ('more vars new'); mehr vars neu: 390ms und diese console.time ('mehr vars neu'); var myOtherObject = {}, myOtherArray = []; für (var i = 0; i <200000; i ++) {var arr = new Array ()}; console.timeEnd ('more vars new'); mehr vars neu: 369ms Rückkehr zur gleichen Zeit
Mohsen
2

Gute Frage. Das erste Beispiel wird als Array-Literal bezeichnet. Dies ist die bevorzugte Methode zum Erstellen von Arrays unter vielen Entwicklern. Es kann sein, dass der Leistungsunterschied dadurch verursacht wird, dass die Argumente des neuen Array () -Aufrufs überprüft und dann das Objekt erstellt werden, während das Literal direkt ein Array erstellt.

Der relativ kleine Leistungsunterschied unterstützt diesen Punkt, denke ich. Sie können den gleichen Test übrigens auch mit dem Objekt- und Objektliteral {} durchführen.

Laurent Zuijdwijk
quelle
1

Das würde Sinn machen

Objektliterale ermöglichen es uns, Code zu schreiben, der viele Funktionen unterstützt und dennoch für die Implementierer unseres Codes relativ einfach ist. Es ist nicht erforderlich, Konstruktoren direkt aufzurufen oder die richtige Reihenfolge der an Funktionen usw. übergebenen Argumente beizubehalten.

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

lnguyen55
quelle
1

Interessant ist auch , dass die Verwendung eines Array-Konstruktors mit einer bestimmten Länge in Google Chrome 70+ viel schneller ist , wenn die Länge des Arrays im Voraus bekannt ist (Elemente werden unmittelbar nach der Erstellung hinzugefügt) .

  • " neues Array ( % ARR_LENGTH% ) " - 100% (schneller) !

  • " [] " - 160-170% (langsamer)

Diagramm mit Ergebnissen der Maßnahmen.

Den Test finden Sie hier - https://jsperf.com/small-arr-init-with-known-length-brackets-vs-new-array/2

Hinweis: Dieses Ergebnis wurde in Google Chrome v.70 + getestet . In Firefox v.70 und IE sind beide Varianten nahezu gleich.

Oleg Zarevennyi
quelle