2015-02-11 15 views
12

Ich habe versucht zu sondieren, dass Plus (+) Umwandlung schneller als parseInt ist mit dem folgenden jsperf und die Ergebnisse überrascht mich:: ParseInt vs Plus-Umwandlung

Parse vs Plus

Vorbereitung Code

<script> 
    Benchmark.prototype.setup = function() { 
    var x = "5555"; 
    }; 
</script> 

Parse Probe

var y = parseInt(x); //<---80 million loops 

Plus-Probe

var y = +x; //<--- 33 million loops 

Der Grund ist, weil ich bin mit „Benchmark.prototype.setup“, um meine Variablen zu erklären, aber ich verstehe nicht, warum

das zweite Beispiel anzeigen :

Parse vs Plus (local variable)

<script> 
    Benchmark.prototype.setup = function() { 
    x = "5555"; 
    }; 
</script> 

Parse Probe

var y = parseInt(x); //<---89 million loops 

Plus-Probe

var y = +x; //<--- 633 million loops 

Kann jemand die Ergebnisse erklären?

Dank

+2

Sie erhalten hier eine bessere Antwort, wenn der Kerncode hier ist, anstatt durch einen Link (ich gab Ihnen ein +1 BTW, da es eine interessante Frage ist). – rfornal

+1

Eine interessante Frage, aber es geht mehr um jsperf und Testfälle als um 'parseInt' vs' + '. Ich denke, dein Titel ist ein wenig irreführend. –

+0

Ich habe die Frage bearbeitet. Ich hoffe es ist jetzt besser –

Antwort

-1

Ich glaube, der Grund ist, weil ParseInt für mehr sieht als nur eine Umwandlung in eine ganze Zahl. Es streift auch alle verbleibenden Text aus der Zeichenfolge, wie wenn ein Pixelwert-Analyse:

var width = parseInt(element.style.width);//return width as integer 

während das Pluszeichen konnte diesen Fall nicht umgehen:

var width = +element.style.width;//returns NaN 

Das Pluszeichen hat eine implizite Konvertierung von String zu nummerieren und nur diese Umwandlung. parseInt versucht zuerst, aus der Zeichenfolge einen Sinn zu machen (wie bei Ganzzahlen, die mit einer Messung versehen sind).

+0

die Frage ist, warum im ersten Fall ** parseInt ** scheint schneller zu sein als ** plus ** –

30

Im zweiten Fall + schneller ist, weil in diesem Fall V8 es tatsächlich aus der Benchmarking-Schleife bewegt - Benchmarking Schleife leer machen.

Dies geschieht aufgrund bestimmter Besonderheiten der aktuellen Optimierungspipeline. Aber bevor wir zu den blutigen Details kommen, möchte ich daran erinnern, wie Benchmark.js funktioniert.

den Testfall messen Sie es Benchmark.prototype.setup schrieb nimmt, die Sie auch zur Verfügung gestellt und den Testfall selbst und generiert dynamisch eine Funktion, die etwa sieht wie folgt aus (einige irrelevante Details Ich Skipping):

function (n) { 
    var start = Date.now(); 

    /* Benchmark.prototype.setup body here */ 
    while (n--) { 
    /* test body here */ 
    } 

    return Date.now() - start; 
} 

Sobald die Funktion Benchmark erstellt wird.js ruft es auf, um Ihre Operation für eine bestimmte Anzahl von Iterationen n zu messen. Dieser Vorgang wird mehrmals wiederholt: Erzeugen Sie eine neue Funktion, rufen Sie sie auf, um eine Messprobe zu sammeln. Die Anzahl der Iterationen wird zwischen den Samples angepasst, um sicherzustellen, dass die Funktion lange genug läuft, um eine sinnvolle Messung zu ermöglichen.

Wichtige Dinge hier zu bemerken ist, dass

  1. sowohl Ihren Fall und Benchmark.prototype.setup die textlich inlined sind;
  2. Es gibt eine Schleife um die Operation, die Sie messen möchten;

Im Wesentlichen diskutieren wir, warum der Code unten mit einer lokalen Variablen x

function f(n) { 
    var start = Date.now(); 

    var x = "5555" 
    while (n--) { 
    var y = +x 
    } 

    return Date.now() - start; 
} 

läuft langsamer als der Code mit globalen Variablen x

function g(n) { 
    var start = Date.now(); 

    x = "5555" 
    while (n--) { 
    var y = +x 
    } 

    return Date.now() - start; 
} 

(Hinweis: dieser Fall lokal genannt wird Variable in der Frage selbst, aber das ist nicht der Fall, x ist global)

Was passiert, wenn Sie diese Funktionen mit ausreichend großen Werten von n ausführen, zum Beispiel f(1e6)?

Aktuelle Optimierung Pipeline implementiert OSR in einer besonderen Art und Weise. Anstatt eine OSR-spezifische Version des optimierten Codes zu generieren und später zu verwerfen, generiert er eine Version, die sowohl für OSR- als auch für normale Einträge verwendet werden kann und sogar wiederverwendet werden kann, wenn OSR in derselben Schleife ausgeführt werden muss. Dies geschieht, indem ein spezieller OSR-Eintragsblock an die richtige Stelle im Kontrollflussdiagramm injiziert wird.

OSR version of the control flow graph

OSR Einsprungblockdeklaration injiziert wird, während SSA IR für die Funktion aufgebaut ist und es eifrig kopiert alle lokalen Variablen aus dem eingehenden OSR Zustand. Als Ergebnis kann V8 nicht sehen, dass lokal x tatsächlich eine Konstante ist und sogar irgendwelche Informationen über seinen Typ verliert. Für nachfolgende Optimierungsdurchläufe sieht x2 so aus, als könnte es alles sein.

Als x2 kann alles Ausdruck sein +x2 kann auch willkürliche Nebenwirkungen haben (z. B. kann es ein Objekt mit valueOf angeschlossen sein). Dies verhindert, dass der Schleifeninvariante-Code-Bewegungsdurchlauf +x2 aus der Schleife heraus bewegt wird.

Warum ist g schneller als? V8 zieht hier einen Trick. Es verfolgt globale Variablen, die Konstanten enthalten: z.B. In diesem Benchmark enthält x immer "5555" V8 ersetzt nur x Zugriff mit seinem Wert und markiert diesen optimierten Code als abhängig von dem Wert x. Wenn jemand den Wert x durch etwas anderes ersetzt, wird der abhängige Code nicht optimiert. Globale Variablen sind ebenfalls nicht Teil des OSR-Zustands und nehmen nicht an der SSA-Umbenennung teil, so dass V8 nicht durch "falsche" φ-Funktionen verwechselt wird, die OSR- und normale Eintrittszustände verschmelzen.Deshalb, wenn V8 g optimiert es endet die Erzeugung des folgenden IR in der Schleife (roter Streifen auf der linken Seite zeigt die Schleife):

IR before LICM

Hinweis: +x wird x * 1 kompiliert, aber das ist nur ein Implementierungsdetail.

Später würde LICM einfach diese Operation ausführen und sie aus der Schleife entfernen, ohne dass die Schleife selbst interessant wäre. Dies wird möglich, weil V8 jetzt weiß, dass beide Operanden der * Primitive sind - so kann es keine Nebenwirkungen geben.

IR after LICM

Und deshalb g schneller ist, weil leere Schleife ziemlich schneller ist offensichtlich als eine nicht-leere.

Dies bedeutet auch, dass die zweite Version des Benchmarks nicht tatsächlich misst, was Sie es messen möchten, und während die erste Version tatsächlich einige der Unterschiede zwischen parseInt(x) und +x Leistung, die mehr durch Glück war zu erfassen: Sie getroffen eine Einschränkung in der aktuellen Optimierungspipeline (Crankshaft) von V8, die verhinderte, dass sie das gesamte Mikrobenmark weg frisst.

+0

... wow. Von Millimeterpapier-Flussdiagrammen zu einer komplexen OSR V8 SSAIR-Optimierungsgrafik. Das schien eine so einfache Frage zu sein, aber das ist eine der kompliziertesten Antworten, die ich je gesehen habe. +1 – Cyoce

Verwandte Themen