v8 Auswirkungen der JavaScript-Leistung von const, let und var?

89

Hat die Verwendung der neuen Schlüsselwörter "let" und "const" unabhängig von funktionalen Unterschieden allgemeine oder spezifische Auswirkungen auf die Leistung im Vergleich zu "var"?

Nach dem Ausführen des Programms:

function timeit(f, N, S) {
    var start, timeTaken;
    var stats = {min: 1e50, max: 0, N: 0, sum: 0, sqsum: 0};
    var i;
    for (i = 0; i < S; ++i) {
        start = Date.now();
        f(N);
        timeTaken = Date.now() - start;

        stats.min = Math.min(timeTaken, stats.min);
        stats.max = Math.max(timeTaken, stats.max);
        stats.sum += timeTaken;
        stats.sqsum += timeTaken * timeTaken;
        stats.N++
    }

    var mean = stats.sum / stats.N;
    var sqmean = stats.sqsum / stats.N;

    return {min: stats.min, max: stats.max, mean: mean, spread: Math.sqrt(sqmean - mean * mean)};
}

var variable1 = 10;
var variable2 = 10;
var variable3 = 10;
var variable4 = 10;
var variable5 = 10;
var variable6 = 10;
var variable7 = 10;
var variable8 = 10;
var variable9 = 10;
var variable10 = 10;

function varAccess(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += variable1;
        sum += variable2;
        sum += variable3;
        sum += variable4;
        sum += variable5;
        sum += variable6;
        sum += variable7;
        sum += variable8;
        sum += variable9;
        sum += variable10;
    }
    return sum;
}

const constant1 = 10;
const constant2 = 10;
const constant3 = 10;
const constant4 = 10;
const constant5 = 10;
const constant6 = 10;
const constant7 = 10;
const constant8 = 10;
const constant9 = 10;
const constant10 = 10;

function constAccess(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += constant1;
        sum += constant2;
        sum += constant3;
        sum += constant4;
        sum += constant5;
        sum += constant6;
        sum += constant7;
        sum += constant8;
        sum += constant9;
        sum += constant10;
    }
    return sum;
}


function control(N) {
    var i, sum;
    for (i = 0; i < N; ++i) {
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
        sum += 10;
    }
    return sum;
}

console.log("ctl = " + JSON.stringify(timeit(control, 10000000, 50)));
console.log("con = " + JSON.stringify(timeit(constAccess, 10000000, 50)));
console.log("var = " + JSON.stringify(timeit(varAccess, 10000000, 50)));

.. Meine Ergebnisse waren die folgenden:

ctl = {"min":101,"max":117,"mean":108.34,"spread":4.145407097016924}
con = {"min":107,"max":572,"mean":435.7,"spread":169.4998820058587}
var = {"min":103,"max":608,"mean":439.82,"spread":176.44417700791374}

Die hier erwähnte Diskussion scheint jedoch auf ein echtes Potenzial für Leistungsunterschiede unter bestimmten Szenarien hinzuweisen: https://esdiscuss.org/topic/performance-concern-with-let-const

sean2078
quelle
Ich denke, das hängt von der Verwendung ab, zum Beispiel sollte die letVerwendung im Blockbereich leistungsfähiger sein als die var, die keinen Blockbereich, sondern nur Funktionsbereich hat.
Adeneo
Wenn ich fragen darf, warum ist das @adeneo?
Sean2078
1
@ sean2078 - Wenn Sie eine Variable deklarieren müssen, die nur in einem Blockbereich lebt, letwürden Sie dies tun und dann Müll sammeln, während dies var, was funktionsbezogen ist, nicht unbedingt auf die gleiche Weise funktionieren würde. Wieder denke ich, es ist so spezifisch für die Nutzung, dass beide letund const kann performanter sein, aber nicht immer sein würde.
Adeneo
1
Ich bin verwirrt darüber, wie der zitierte Code einen Unterschied zwischen varund demonstrieren soll let: Er wird überhaupt nicht verwendet let.
TJ Crowder
1
Derzeit ist nicht - nur const vs. var .. Ursprünglich von gist.github.com/srikumarks/1431640 bezogen (Gutschrift an srikumarks), jedoch wurde die Anfrage gestellt, Code in Frage zu stellen
sean2078

Antworten:

119

TL; DR

Theoretisch eine nicht optimierte Version dieser Schleife:

for (let i = 0; i < 500; ++i) {
    doSomethingWith(i);
}

ist möglicherweise langsamer als eine nicht optimierte Version derselben Schleife mit var:

for (var i = 0; i < 500; ++i) {
    doSomethingWith(i);
}

weil für jede Schleifeniteration mit eine andere i Variable erstellt wird let, während es nur eine imit gibt var.

Dagegen spricht die Tatsache, dass das varso hochgezogen wird, dass es außerhalb der Schleife deklariert wird, während das letnur innerhalb der Schleife deklariert wird, was einen Optimierungsvorteil bieten kann.

In der Praxis führen moderne JavaScript-Engines hier im Jahr 2018 eine ausreichende Selbstbeobachtung der Schleife durch, um zu wissen, wann sie diesen Unterschied beseitigen können. (Schon vorher hat Ihre Schleife wahrscheinlich genug Arbeit geleistet, dass der zusätzliche letOverhead ohnehin ausgewaschen wurde. Aber jetzt müssen Sie sich nicht einmal mehr darum kümmern.)

Passen Sie auf synthetische Benchmarks auf, da diese extrem leicht falsch sind, und lösen Sie JavaScript-Engine-Optimierer auf eine Weise aus, die echter Code nicht tut (sowohl auf gute als auch auf schlechte Weise). Wenn Sie jedoch einen synthetischen Benchmark wünschen, ist hier einer:

Es heißt, dass es keinen signifikanten Unterschied in diesem synthetischen Test auf V8 / Chrome oder SpiderMonkey / Firefox gibt. (Wiederholte Tests in beiden Browsern haben den einen oder den anderen Gewinn und in beiden Fällen innerhalb einer Fehlergrenze.) Aber auch hier handelt es sich um einen synthetischen Benchmark, nicht um Ihren Code. Sorgen Sie sich um die Leistung Ihres Codes, wenn und wenn Ihr Code ein Leistungsproblem aufweist.

Aus letStilgründen bevorzuge ich den Scoping-Vorteil und den Closure-in-Loops-Vorteil, wenn ich die Loop-Variable in einem Closure verwende.

Einzelheiten

Der wichtige Unterschied zwischen varund letin einer forSchleife besteht darin, dass ifür jede Iteration ein anderer erstellt wird. Es befasst sich mit dem klassischen Problem "Closures in Loop":

Das Erstellen des neuen EnvironmentRecord für jeden Schleifenkörper ( Spezifikationslink ) ist Arbeit, und Arbeit braucht Zeit, weshalb die letVersion theoretisch langsamer als die varVersion ist.

Der Unterschied ist jedoch nur wichtig, wenn Sie eine Funktion (Schließung) innerhalb der verwendeten Schleife erstellen i, wie ich es in diesem Beispiel für ein ausführbares Snippet oben getan habe. Andernfalls kann die Unterscheidung nicht beobachtet und wegoptimiert werden.

Hier im Jahr 2018 sieht es so aus, als würde V8 (und SpiderMonkey in Firefox) eine ausreichende Selbstbeobachtung durchführen, sodass in einer Schleife, die nicht die letSemantik der Variablen pro Iteration verwendet , keine Leistungskosten anfallen. Siehe diesen Test .


In einigen Fällen bietet sich constmöglicherweise eine Optimierungsmöglichkeit, vardie insbesondere bei globalen Variablen nicht möglich wäre.

Das Problem mit einer globalen Variablen ist, dass sie global ist. Jeder Code kann überall darauf zugreifen. Wenn Sie also eine Variable deklarieren, mit varder Sie niemals Änderungen vornehmen möchten (und die Sie niemals in Ihrem Code ändern), kann die Engine nicht davon ausgehen, dass sie sich aufgrund von später geladenem oder ähnlichem Code niemals ändern wird.

Mit constteilen Sie der Engine jedoch ausdrücklich mit, dass sich der Wert nicht ändern kann¹. Sie können also jede gewünschte Optimierung durchführen, einschließlich der Ausgabe eines Literals anstelle eines variablen Verweises auf Code, der es verwendet, da Sie wissen, dass die Werte nicht geändert werden können.

¹ Denken Sie daran, dass bei Objekten der Wert eine Referenz auf das Objekt ist, nicht auf das Objekt selbst. Mit const o = {}können Sie also den Status des Objekts ( o.answer = 42) ändern , aber Sie können nicht oauf ein neues Objekt verweisen (da dies das Ändern der darin enthaltenen Objektreferenz erfordern würde).


Bei Verwendung letoder constin varähnlichen Situationen ist es unwahrscheinlich, dass sie eine andere Leistung aufweisen. Diese Funktion sollte genau die gleiche Leistung haben, egal ob Sie varoder letzum Beispiel:

function foo() {
    var i = 0;
    while (Math.random() < 0.5) {
        ++i;
    }
    return i;
}

Es ist natürlich alles unwahrscheinlich und etwas, worüber man sich nur Sorgen machen muss, wenn ein echtes Problem zu lösen ist.

TJ Crowder
quelle
Vielen Dank für die Antwort - ich stimme zu, daher habe ich die Verwendung von var für Schleifenoperationen standardisiert, wie in Ihrem ersten for-Schleifenbeispiel angegeben, und let / const für alle anderen Deklarationen unter der Annahme, dass der Leistungsunterschied im Wesentlichen nicht vorhanden ist, wie der Leistungstest scheint vorerst anzeigen. Möglicherweise werden später Optimierungen für const hinzugefügt. Das heißt, es sei denn, jemand anderes kann anhand eines Codebeispiels einen erkennbaren Unterschied feststellen.
Sean2078
@ sean2078: Ich verwende auch letim Loop-Beispiel. Der Leistungsunterschied ist im Fall von 99,999% einfach keine Sorge wert.
TJ Crowder
2
Ab Mitte 2018 haben die Versionen mit let und var in Chrome die gleiche Geschwindigkeit, sodass es jetzt keinen Unterschied mehr gibt.
Max
1
@DanM.: Gute Nachrichten, die Optimierung scheint zumindest in V8 und SpiderMonkey aufgeholt zu haben. :-)
TJ Crowder
1
Vielen Dank. Fair genug.
Hypers
18

"LET" IST IN LOOP-ERKLÄRUNGEN BESSER

Mit einem einfachen Test (5 Mal) im Navigator wie folgt:

// WITH VAR
console.time("var-time")
for(var i = 0; i < 500000; i++){}
console.timeEnd("var-time")

Die durchschnittliche Ausführungszeit beträgt mehr als 2,5 ms

// WITH LET
console.time("let-time")
for(let i = 0; i < 500000; i++){}
console.timeEnd("let-time")

Die durchschnittliche Ausführungszeit beträgt mehr als 1,5 ms

Ich fand, dass die Schleifenzeit mit let besser ist.

Amn
quelle
6
Wenn ich dies in Firefox 65.0 ausführe, habe ich mittlere Geschwindigkeiten von var=138.8msund let=4ms. Das ist kein Tippfehler, letist
Katamari
6
Ich habe dies gerade in Node v12.5 getestet. Ich fand die mittleren Geschwindigkeiten var=2.6msund let=1.0ms. Einlassknoten ist also etwas mehr als doppelt so schnell.
Kane Hooper
2
Nur um den üblichen Punkt zu verdeutlichen, dass Leistungstests in Gegenwart von Optimierern schwierig sind: Ich denke, die let-Schleife wird vollständig weg optimiert - let existiert nur innerhalb des Blocks und die Schleife hat keine Nebenwirkungen und V8 ist klug genug, um zu wissen, dass dies möglich ist Entfernen Sie einfach den Block und dann die Schleife. Die var-Deklaration wird gehisst, sodass sie das nicht wissen kann. Ihre Schleifen erhalten 1 ms / 0,4 ms. Wenn ich jedoch für beide eine Variable j (var oder let) außerhalb der Schleife habe, die ebenfalls inkrementiert ist, erhalte ich 1 ms / 1,5 ms. dh var loop keine Änderung, lassen Sie die Schleife jetzt länger dauern.
Euan Smith
@KaneHooper - Wenn Sie in Firefox einen fünffachen Unterschied haben, muss es der leere Schleifenkörper gewesen sein, der dies getan hat. Echte Loops haben keine leeren Körper.
TJ Crowder
1
Achten Sie auf synthetische Benchmarks , insbesondere solche mit Schleifen mit leeren Körpern. Wenn Sie tatsächlich etwas in der Schleife tun, deutet dieser synthetische Benchmark (der wiederum aufpasst! :-)) darauf hin, dass es keinen signifikanten Unterschied gibt. Ich habe meiner Antwort auch eine hinzugefügt, damit sie vor Ort ist (nicht wie die jsPerf-Tests, die bei mir immer wieder verschwunden sind. :-)). Wiederholte Läufe zeigen den einen oder den anderen Gewinn. Sicher nichts aussagekräftiges.
TJ Crowder
9

Die Antwort von TJ Crowder ist so hervorragend.

Hier ist eine Ergänzung von: "Wann würde ich das Beste für mein Geld bekommen, wenn ich vorhandene var-Deklarationen zu const bearbeite?"

Ich habe festgestellt, dass der größte Leistungsschub mit "exportierten" Funktionen zu tun hat.

Wenn also Datei A, B, R und Z eine "Dienstprogramm" -Funktion in Datei U aufrufen, die üblicherweise über Ihre App verwendet wird, kann die Umschaltung dieser Dienstprogrammfunktion auf "const" und der Verweis der übergeordneten Datei auf eine Konstante auftreten etwas verbesserte Leistung. Es schien mir nicht messbar schneller zu sein, aber der Gesamtspeicherverbrauch wurde für meine grob monolithische Frankenstein-ed-App um etwa 1-3% reduziert. Wenn Sie viel Geld in der Cloud oder auf Ihrem Baremetal-Server ausgeben, kann dies ein guter Grund sein, 30 Minuten damit zu verbringen, einige dieser var-Deklarationen zu durchsuchen und auf const zu aktualisieren.

Mir ist klar, dass Sie, wenn Sie lesen, wie const, var und unter der Decke arbeiten lassen, wahrscheinlich bereits das Obige abgeschlossen haben ... aber für den Fall, dass Sie darüber "geschaut" haben: D.

Soweit ich mich an das Benchmarking auf Knoten v8.12.0 erinnere, als ich das Update durchführte, ging meine App von einem Leerlaufverbrauch von ~ 240 MB RAM auf ~ 233 MB RAM über.

isaacdre
quelle
3

Die Antwort von TJ Crowder ist sehr gut, aber:

  1. 'let' soll den Code lesbarer und nicht leistungsfähiger machen
  2. theoretisch wird let langsamer sein als var
  3. In der Praxis kann der Compiler ein unvollständiges Programm nicht vollständig lösen (statische Analyse), so dass es manchmal die Optimierung verfehlt
  4. In jedem Fall erfordert die Verwendung von 'let' mehr CPU für die Selbstbeobachtung. Die Bank muss gestartet werden, wenn Google v8 mit dem Parsen beginnt
  5. Wenn die Selbstbeobachtung fehlschlägt, wird 'let' den V8-Garbage Collector stark belasten, und es ist mehr Iteration erforderlich, um ihn freizugeben / wiederzuverwenden. Es wird auch mehr RAM verbrauchen. Die Bank muss diese Punkte berücksichtigen
  6. Google Closure wird ...

Der Effekt der Leistungslücke zwischen var und let ist im realen Gesamtprogramm und nicht in einer einzelnen Basisschleife zu sehen.

Wenn Sie let verwenden, wo Sie es nicht müssen, ist Ihr Code weniger lesbar.

Michael Valve
quelle
Aus Interesse - nach welcher Theorie ist letlangsamer als var? Besonders angesichts des Konsenses in den Kommentaren zu der obigen Antwort, der zeigt, dass es schneller ist?
JamesTheAwesomeDude