Beeinträchtigt die Verwendung anonymer Funktionen die Leistung?


86

Ich habe mich gefragt, ob es einen Leistungsunterschied zwischen der Verwendung benannter Funktionen und anonymer Funktionen in Javascript gibt.

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = function() {
        // do something
    };
}

vs.

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

Das erste ist aufgeräumter, da es Ihren Code nicht mit selten verwendeten Funktionen überfüllt. Aber ist es wichtig, dass Sie diese Funktion mehrmals neu deklarieren?


Ich weiß, dass es nicht in Frage kommt, aber in Bezug auf Code-Sauberkeit / Lesbarkeit denke ich, dass der „richtige Weg“ irgendwo in der Mitte liegt. "Unordnung" selten verwendeter Funktionen der obersten Ebene ist ärgerlich, aber auch stark verschachtelter Code, der stark von anonymen Funktionen abhängt, die im Einklang mit ihrem Aufruf deklariert werden (denken Sie an die Rückrufhölle von node.j). Sowohl das erstere als auch das letztere können das Debuggen / Ausführen der Ausführung erschweren.
Zac B

Die folgenden Leistungstests führen die Funktion für Tausende von Iterationen aus. Selbst wenn Sie einen wesentlichen Unterschied feststellen, wird dies in den meisten Anwendungsfällen in Iterationen dieser Reihenfolge nicht der Fall sein. Daher ist es besser, zu wählen, was Ihren Anforderungen entspricht, und die Leistung für diesen speziellen Fall zu ignorieren.
Benutzer

@nickf natürlich seine zu alte Frage, aber siehe die neue aktualisierte Antwort
Chandan Pasunoori

Antworten:


88

Das Leistungsproblem hierbei sind die Kosten für die Erstellung eines neuen Funktionsobjekts bei jeder Iteration der Schleife und nicht die Tatsache, dass Sie eine anonyme Funktion verwenden:

for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = function() {
        // do something    
    };
}

Sie erstellen tausend verschiedene Funktionsobjekte, obwohl sie denselben Code enthalten und nicht an den lexikalischen Bereich gebunden sind ( Abschluss ). Das Folgende scheint andererseits schneller zu sein, da es den Array-Elementen in der gesamten Schleife einfach dieselbe Funktionsreferenz zuweist :

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

Wenn Sie die anonyme Funktion vor dem Betreten der Schleife erstellen und dann nur innerhalb der Schleife Verweise auf die Array-Elemente zuweisen, werden Sie feststellen, dass es im Vergleich zur benannten Funktionsversion keinerlei Leistung oder semantischen Unterschied gibt:

var handler = function() {
    // do something    
};
for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = handler;
}

Kurz gesagt, es gibt keine beobachtbaren Leistungskosten für die Verwendung anonymer überbenannter Funktionen.

Abgesehen davon kann es von oben erscheinen, dass es keinen Unterschied gibt zwischen:

function myEventHandler() { /* ... */ }

und:

var myEventHandler = function() { /* ... */ }

Ersteres ist eine Funktionsdeklaration, während letzteres eine variable Zuordnung zu einer anonymen Funktion ist. Obwohl sie den gleichen Effekt zu haben scheinen, behandelt JavaScript sie etwas anders. Um den Unterschied zu verstehen, empfehle ich, „ Mehrdeutigkeit der JavaScript-Funktionsdeklaration “ zu lesen .

Die tatsächliche Ausführungszeit für jeden Ansatz wird weitgehend von der Implementierung des Compilers und der Laufzeit durch den Browser bestimmt. Einen vollständigen Vergleich der modernen Browserleistung finden Sie auf der JS Perf-Website


Sie haben die Klammern vor dem Funktionskörper vergessen. Ich habe es gerade getestet, sie sind erforderlich.
Chinoto Vokro

es scheint, dass die Benchmark-Ergebnisse sehr von der Js-Engine abhängig sind!
Aleclofabbro

3
Gibt es nicht einen Fehler im JS Perf-Beispiel: Fall 1 definiert nur die Funktion, während Fall 2 und 3 die Funktion versehentlich aufzurufen scheinen .
bluenote10

Bedeutet dies, dass es bei der Entwicklung von node.jsWebanwendungen besser ist, die Funktionen außerhalb des Anforderungsflusses zu erstellen und als Rückrufe zu übergeben, als anonyme Rückrufe zu erstellen?
Xavier T Mukodi

22

Hier ist mein Testcode:

var dummyVar;
function test1() {
    for (var i = 0; i < 1000000; ++i) {
        dummyVar = myFunc;
    }
}

function test2() {
    for (var i = 0; i < 1000000; ++i) {
        dummyVar = function() {
            var x = 0;
            x++;
        };
    }
}

function myFunc() {
    var x = 0;
    x++;
}

document.onclick = function() {
    var start = new Date();
    test1();
    var mid = new Date();
    test2();
    var end = new Date();
    alert ("Test 1: " + (mid - start) + "\n Test 2: " + (end - mid));
}

Die Ergebnisse:
Test 1: 142 ms Test 2: 1983 ms

Es scheint, dass die JS-Engine nicht erkennt, dass es sich um dieselbe Funktion in Test2 handelt, und sie jedes Mal kompiliert.


3
In welchem ​​Browser wurde dieser Test durchgeführt?
Andynil

5
Zeiten für mich auf Chrome 23: (2 ms / 17 ms), IE9: (20 ms / 83 ms), FF 17: (2 ms / 96 ms)
Davy8

Ihre Antwort verdient mehr Gewicht. Meine Zeiten auf Intel i5 4570S: Chrome 41 (1/9), IE11 (1/25), FF36 (1/14). Offensichtlich ist die anonyme Funktion in einer Schleife schlechter.
ThisClark

3
Dieser Test ist nicht so nützlich, wie es scheint. In keinem Beispiel wird die Innenfunktion tatsächlich ausgeführt. Tatsächlich zeigt dieser Test nur, dass das 10000000-fache Erstellen einer Funktion schneller ist als das einmalige Erstellen einer Funktion.
Nucleon

2

Als allgemeines Entwurfsprinzip sollten Sie vermeiden, denselben Code mehrmals zu implementieren. Stattdessen sollten Sie allgemeinen Code in eine Funktion herausheben und diese (allgemeine, gut getestete, leicht zu ändernde) Funktion von mehreren Stellen aus ausführen.

Wenn Sie (im Gegensatz zu dem, was Sie aus Ihrer Frage ableiten) die interne Funktion einmal deklarieren und diesen Code einmal verwenden (und nichts anderes in Ihrem Programm identisch haben), wird eine anonome Funktion wahrscheinlich (das ist eine Vermutung, Leute) von der genauso behandelt Compiler als normal benannte Funktion.

Es ist eine sehr nützliche Funktion in bestimmten Fällen, sollte aber in vielen Situationen nicht verwendet werden.


1

Ich würde keinen großen Unterschied erwarten, aber wenn es einen gibt, wird er wahrscheinlich je nach Skript-Engine oder Browser variieren.

Wenn Sie feststellen, dass der Code leichter zu erfassen ist, ist die Leistung kein Problem, es sei denn, Sie erwarten, dass Sie die Funktion millionenfach aufrufen.


1

Anonyme Objekte sind schneller als benannte Objekte. Das Aufrufen von mehr Funktionen ist jedoch teurer und in einem Maße, das die Einsparungen, die Sie durch die Verwendung anonymer Funktionen erzielen könnten, in den Schatten stellt. Jede aufgerufene Funktion wird zum Aufrufstapel hinzugefügt, wodurch ein kleiner, aber nicht trivialer Overhead entsteht.

Aber es sei denn, Sie schreiben Verschlüsselungs- / Entschlüsselungsroutinen oder etwas ähnlich Leistungsempfindliches, wie viele andere angemerkt haben, ist es immer besser, für eleganten, einfach zu lesenden Code gegenüber schnellem Code zu optimieren.

Angenommen, Sie schreiben gut strukturierten Code, sollten Geschwindigkeitsprobleme in der Verantwortung derjenigen liegen, die die Interpreter / Compiler schreiben.


1

Wo wir einen Einfluss auf die Leistung haben können, liegt in der Deklaration von Funktionen. Hier ist ein Benchmark für die Deklaration von Funktionen im Kontext einer anderen Funktion oder außerhalb:

http://jsperf.com/function-context-benchmark

In Chrome ist der Vorgang schneller, wenn wir die Funktion außerhalb deklarieren, in Firefox ist es umgekehrt.

In einem anderen Beispiel sehen wir, dass die innere Funktion, wenn sie keine reine Funktion ist, auch in Firefox einen Leistungsmangel aufweist: http://jsperf.com/function-context-benchmark-3


0

Was Ihre Schleife in einer Vielzahl von Browsern, insbesondere in IE-Browsern, definitiv schneller macht, ist die folgende Schleife:

for (var i = 0, iLength = imgs.length; i < iLength; i++)
{
   // do something
}

Sie haben eine beliebige 1000 in die Schleifenbedingung eingegeben, aber Sie erhalten meine Abweichung, wenn Sie alle Elemente im Array durchgehen möchten.


0

Eine Referenz wird fast immer langsamer sein als das, worauf sie sich bezieht. Stellen Sie sich das so vor - nehmen wir an, Sie möchten das Ergebnis der Addition von 1 + 1 drucken. Was sinnvoller ist:

alert(1 + 1);

oder

a = 1;
b = 1;
alert(a + b);

Mir ist klar, dass dies eine sehr vereinfachte Sichtweise ist, aber es ist illustrativ, oder? Verwenden Sie eine Referenz nur, wenn sie mehrmals verwendet werden soll. Welches dieser Beispiele ist beispielsweise sinnvoller:

$(a.button1).click(function(){alert('you clicked ' + this);});
$(a.button2).click(function(){alert('you clicked ' + this);});

oder

function buttonClickHandler(){alert('you clicked ' + this);}
$(a.button1).click(buttonClickHandler);
$(a.button2).click(buttonClickHandler);

Die zweite ist eine bessere Übung, auch wenn sie mehr Zeilen enthält. Hoffentlich ist das alles hilfreich. (und die jquery-Syntax hat niemanden abgeschreckt)


0

@nickf

(Ich wünschte, ich hätte den Repräsentanten, um nur zu kommentieren, aber ich habe diese Seite gerade erst gefunden.)

Mein Punkt ist, dass hier Verwirrung zwischen benannten / anonymen Funktionen und dem Anwendungsfall des Ausführens + Kompilierens in einer Iteration besteht. Wie ich gezeigt habe, ist der Unterschied zwischen anon + named an sich vernachlässigbar - ich sage, es ist der Anwendungsfall, der fehlerhaft ist.

Es scheint mir offensichtlich, aber wenn nicht, denke ich, ist der beste Rat "mach keine dummen Dinge" (von denen die ständige Blockverschiebung + Objekterstellung dieses Anwendungsfalls eine ist) und wenn du dir nicht sicher bist, teste!



0

@nickf

Das ist allerdings ein ziemlich fetter Test. Sie vergleichen dort die Ausführungs- und Kompilierungszeit, die offensichtlich Methode 1 (N-mal kompiliert, abhängig von der JS-Engine) mit Methode 2 (einmal kompilieren) kosten wird. Ich kann mir keinen JS-Entwickler vorstellen, der seine Probezeit so schreiben würde.

Ein weitaus realistischerer Ansatz ist die anonyme Zuweisung, da Sie sie tatsächlich für Ihr Dokument verwenden. Die onclick-Methode ähnelt eher der folgenden, was die anon-Methode in der Tat leicht begünstigt.

Verwenden eines ähnlichen Testframeworks wie Ihres:


function test(m)
{
    for (var i = 0; i < 1000000; ++i) 
    {
        m();
    }
}

function named() {var x = 0; x++;}

var test1 = named;

var test2 = function() {var x = 0; x++;}

document.onclick = function() {
    var start = new Date();
    test(test1);
    var mid = new Date();
    test(test2);
    var end = new Date();
    alert ("Test 1: " + (mid - start) + "ms\n Test 2: " + (end - mid) + "ms");
}

0

Wie in den Kommentaren zu @nickf Antwort darauf hingewiesen: Die Antwort auf

Erstellt eine Funktion einmal schneller als millionenfach

ist einfach ja. Aber wie sein JS Perf zeigt, ist es nicht um den Faktor eine Million langsamer, was zeigt, dass es mit der Zeit tatsächlich schneller wird.

Die interessantere Frage für mich ist:

Wie vergleicht sich ein wiederholter Erstellen + Ausführen mit einem einmaligen + wiederholten Lauf ?

Wenn eine Funktion eine komplexe Berechnung durchführt, ist die Zeit zum Erstellen des Funktionsobjekts höchstwahrscheinlich vernachlässigbar. Aber was ist mit dem Overhead von create in Fällen , in denen der Lauf schnell ist? Zum Beispiel:

// Variant 1: create once
function adder(a, b) {
  return a + b;
}
for (var i = 0; i < 100000; ++i) {
  var x = adder(412, 123);
}

// Variant 2: repeated creation via function statement
for (var i = 0; i < 100000; ++i) {
  function adder(a, b) {
    return a + b;
  }
  var x = adder(412, 123);
}

// Variant 3: repeated creation via function expression
for (var i = 0; i < 100000; ++i) {
  var x = (function(a, b) { return a + b; })(412, 123);
}

Diese JS Perf zeigt, dass das einmalige Erstellen der Funktion erwartungsgemäß schneller ist. Selbst bei einem sehr schnellen Vorgang wie einem einfachen Hinzufügen beträgt der Aufwand für das wiederholte Erstellen der Funktion nur wenige Prozent.

Der Unterschied wird wahrscheinlich nur in Fällen signifikant, in denen das Erstellen des Funktionsobjekts komplex ist, während eine vernachlässigbare Laufzeit beibehalten wird, z. B. wenn der gesamte Funktionskörper in eine eingeschlossen ist if (unlikelyCondition) { ... }.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.