JavaScript loop performance - Warum soll der Iterator schneller in Richtung 0 dekrementiert werden als inkrementiert?

52

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").

    
Soundlink 19.08.2010, 10:09
quelle

11 Antworten

55

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.

    
Mike Dunlavey 23.08.2010 20:01
quelle
22

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

    
GenericTypeTea 19.08.2010 10:18
quelle
13

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.

    
Gumbo 19.08.2010 10:23
quelle
6

Was ist mit einer umgekehrten while-Schleife? Dann:

%Vor%

IMO dieses ist mindestens ein lesbarer Code als for(i=length; i--;)

    
Marco Demaio 23.08.2010 20:53
quelle
3

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.

    
nabrown78 10.07.2012 00:20
quelle
2

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.

    
icanhazstring 05.07.2012 12:07
quelle
2

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:

%Vor%

Es hängt von der Plattform- und Array-Länge ab, ob length = array.length einen beträchtlichen positiven Effekt hat, aber normalerweise nicht:

%Vor%

Aktuelle V8-Versionen (Chrome, Node) haben Optimierungen für array.length , so dass length = array.length dort auf jeden Fall effizient weggelassen werden kann.

    
estus 21.02.2017 15:50
quelle
1

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.

    
AlexanderMP 19.08.2010 10:17
quelle
1

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.

    
Crashworks 19.08.2010 10:21
quelle
1

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%     
naikus 19.08.2010 10:20
quelle
1

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

%Vor%

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

%Vor%
    
BenG 16.03.2017 17:27
quelle

Tags und Links