In seinem Buch Even Faster Web Sites schreibt Steve Sounders dass ein einfacher Weg, die Leistung einer Schleife zu verbessern, darin besteht, den Iterator gegen 0 zu dekrementieren, anstatt auf die Gesamtlänge zu erhöhen ( tatsächlich wurde das Kapitel von Nicholas C. Zakas geschrieben). Diese Änderung kann je nach Komplexität der einzelnen Iterationen zu Einsparungen von bis zu 50% gegenüber der ursprünglichen Ausführungszeit führen. Zum Beispiel:
%Vor% Dies ist fast identisch für die for
-Schleife, die do-while
-Schleife und die while
-Schleife.
Ich frage mich, was ist der Grund dafür? Warum wird der Iterator so viel schneller dekrementiert? (Ich interessiere mich für den technischen Hintergrund und nicht für Benchmarks, die diese Behauptung belegen.)
EDIT: Auf den ersten Blick sieht die hier verwendete Schleifensyntax falsch aus. Es gibt keine length-1
oder i>=0
, also lassen Sie uns klären (ich war auch verwirrt).
Hier ist die allgemeine Syntax für die Schleifensyntax:
%Vor% Anfangsausdruck - var i=length
Diese Variablendeklaration wird zuerst ausgewertet.
Bedingung - i--
Dieser Ausdruck wird vor jeder Schleifeniteration ausgewertet. Es dekrementiert die Variable vor dem ersten Durchgang durch die Schleife. Wenn dieser Ausdruck als false
ausgewertet wird, endet die Schleife. In JavaScript ist 0 == false
, wenn i
schließlich gleich 0
ist, wird es als false
interpretiert und die Schleife endet.
Endausdruck
Dieser Ausdruck wird am Ende jeder Schleifeniteration ausgewertet (vor der nächsten Auswertung der Bedingung ). Es wird hier nicht benötigt und ist leer. Alle drei Ausdrücke sind in einer for-Schleife optional.
Die for-Schleife-Syntax ist nicht Teil der Frage, aber weil es ein wenig ungewöhnlich ist, denke ich, es ist interessant, es zu klären. Und vielleicht ist ein Grund, dass es schneller ist, weil es weniger Ausdrücke verwendet ( 0 == false
"Trick").
Ich bin nicht sicher über Javascript, und unter modernen Compilern ist es wahrscheinlich egal, aber in den "alten Tagen" diesen Code:
%Vor%würde
erzeugen %Vor%während der rückwärts zählende Code
%Vor%würde
erzeugen %Vor%In der Schleife werden nur zwei zusätzliche Anweisungen anstelle von vier Befehlen ausgeführt.
Ich glaube, der Grund liegt darin, dass Sie den Schleifenendpunkt mit 0 vergleichen, was schneller ist als der Vergleich von < length
(oder einer anderen JS-Variablen).
Dies liegt daran, dass die Ordnungsoperatoren <, <=, >, >=
polymorph sind. Daher benötigen diese Operatoren Typprüfungen auf der linken und rechten Seite des Operators, um zu bestimmen, welches Vergleichsverhalten verwendet werden soll.
Hier gibt es einige sehr gute Benchmarks:
Was ist die schnellste Methode zum Codieren einer Schleife? in JavaScript
Es ist leicht zu sagen, dass eine Iteration weniger Anweisungen haben kann. Vergleichen wir diese beiden einfach:
%Vor% Wenn Sie jeden Variablenzugriff und jeden Operator als eine Anweisung zählen, verwendet die ehemalige for
-Schleife 5 Anweisungen (lesen Sie i
, lesen length
, evaluieren i<length
, test (i<length) == true
, inkrement i
) während letzterer nur 3 Anweisungen verwendet (lesen Sie i
, test i == true
, dekrement i
). Das ist ein Verhältnis von 5: 3.
Was ist mit einer umgekehrten while-Schleife? Dann:
%Vor% IMO dieses ist mindestens ein lesbarer Code als for(i=length; i--;)
Ich habe auch Loop-Geschwindigkeit untersucht und war interessiert, diesen Leckerbissen über Dekrementieren schneller als Inkrementieren zu finden. Ich muss jedoch noch einen Test finden, der dies zeigt. Es gibt viele Loop-Benchmarks auf Jsperf. Hier ist einer, der das Dekrementieren testet:
Allerdings ist die Caching-Länge (empfohlen auch das Buch von Steve Souders) eine gewinnbringende Optimierung.
Es gibt eine noch "performantere" Version davon. Da jedes Argument in For-Schleifen optional ist, können Sie sogar die erste überspringen.
%Vor% Damit überspringst du sogar die Prüfung auf [initial-expression]
. Sie haben also nur noch eine Operation übrig.
for
Inkrement vs. Dekrement in 2017 In modernen JS-Engines ist das Inkrementieren in for
loops im Allgemeinen schneller als das dekrementieren (basierend auf persönlichen Benchmark.js-Tests), auch konventioneller:
Es hängt von der Plattform- und Array-Länge ab, ob length = array.length
einen beträchtlichen positiven Effekt hat, aber normalerweise nicht:
Aktuelle V8-Versionen (Chrome, Node) haben Optimierungen für array.length
, so dass length = array.length
dort auf jeden Fall effizient weggelassen werden kann.
Ich habe einen Benchmark für C # und C ++ (ähnliche Syntax) durchgeführt. Dort unterscheidet sich die Performance im Wesentlichen in for
loops, im Vergleich zu do while
oder while
. In C ++ ist die Leistung beim Inkrementieren höher. Es kann auch vom Compiler abhängen.
In Javascript, ich denke, es hängt alles vom Browser ab (Javascript Engine), aber dieses Verhalten ist zu erwarten. Javascript ist für das Arbeiten mit DOM optimiert. Stellen Sie sich vor, Sie durchlaufen eine Sammlung von DOM-Elementen, die Sie bei jeder Iteration erhalten, und Sie erhöhen einen Zähler, wenn Sie sie entfernen müssen. Du entfernst das Element 0
und dann 1
element, aber dann überspringst du das Element 0
. Bei einer Rückwärtsschleife verschwindet dieses Problem. Ich weiß, dass das angegebene Beispiel nicht nur das richtige ist, aber ich stieß auf Situationen, in denen ich Elemente aus einer sich ständig ändernden Objektsammlung löschen musste.
Da Rückwärts-Schleifen häufiger unvermeidlich sind als Vorwärts-Schleifen, nehme ich an, dass die JS-Engine nur dafür optimiert ist.
Hast du es selbst festgelegt? Herr Sounders könnte mit modernen Dolmetschern falsch liegen. Dies ist genau die Art von Optimierung, in der ein guter Compilerschreiber einen großen Unterschied machen kann.
Ich bin mir nicht sicher, ob es schneller ist, aber ein Grund, den ich sehe, ist, dass wenn Sie über ein Array von großen Elementen mit Inkrement iterieren, Sie schreiben:
%Vor%Sie greifen im Wesentlichen auf die Längeneigenschaft des Arrays N (Anzahl der Elemente) mal zu. Wenn Sie dagegen dekrementieren, greifen Sie nur einmal darauf zu. Das könnte ein Grund sein.
Sie können die Inkrementierungsschleife aber auch wie folgt schreiben:
%Vor%In modernen JS-Engines ist der Unterschied zwischen Forward- und Reverse-Loops fast nicht mehr existent. Aber der Leistungsunterschied kommt auf zwei Dinge an:
a) zusätzliche Suche jeder Längeneigenschaft jedes Zyklus
Dies ist der größte Leistungsgewinn einer Reverse-Schleife und kann offensichtlich auf Forward-Loops angewendet werden.
b) zusätzliche variable Zuweisung.
Der kleinere Vorteil einer Umkehrschleife besteht darin, dass sie nur eine Variablenzuordnung anstelle von 2 erfordert
Tags und Links javascript loops performance