匿名関数の使用はパフォーマンスに影響しますか?


89

私は疑問に思っていましたが、Javascriptで名前付き関数と無名関数を使用することの間にパフォーマンスの違いはありますか?

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;
}

1つ目は、めったに使用されない関数でコードが乱雑にならないため、より整理されていますが、その関数を複数回再宣言することは重要ですか?


私はそれが問題ではないことを知っていますが、コードの清潔さ/読みやすさに関しては、「正しい方法」は中間のどこかにあると思います。めったに使用されないトップレベル関数の「クラッター」は煩わしいですが、呼び出しに沿って宣言された無名関数に大きく依存する、ネストの多いコードも同様です(node.jsコールバック地獄を考えてください)。前者と後者の両方が、デバッグ/実行トレースを困難にする可能性があります。
ザックB

以下のパフォーマンステストでは、関数を数千回の反復で実行します。大きな違いが見られたとしても、ユースケースの大部分はその順序の反復でこれを実行しません。したがって、ニーズに合ったものを選択し、この特定のケースのパフォーマンスを無視することをお勧めします。
ユーザー

@nickfはもちろん古すぎる質問ですが、新しく更新された回答を参照してください
Chandan Pasunoori 2014

回答:


89

ここでのパフォーマンスの問題は、ループの各反復で新しい関数オブジェクトを作成するコストであり、無名関数を使用するという事実ではありません。

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

同じコード本体を持ち、字句スコープ(クロージャ)にバインドされていない場合でも、1000個の異なる関数オブジェクトを作成しています。一方、以下は、ループ全体で配列要素に同じ関数参照を割り当てるだけなので、高速に見えます。

function myEventHandler() {
    // do something
}

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

ループに入る前に無名関数を作成し、ループ内で配列要素にのみ無名関数への参照を割り当てる場合、名前付き関数のバージョンと比較した場合、パフォーマンスやセマンティックの違いはまったくありません。

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

要するに、名前付き関数に対して匿名を使用することによる観察可能なパフォーマンスコストはありません。

余談ですが、上から見ると、次の間に違いはないように見える場合があります。

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

そして:

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

前者は関数宣言ですが、後者は無名関数への変数割り当てです。それらは同じ効果を持っているように見えるかもしれませんが、JavaScriptはそれらをわずかに異なって扱います。違いを理解するために、「JavaScript関数宣言のあいまいさ」を読むことをお勧めします

アプローチの実際の実行時間は、主にブラウザーのコンパイラーとランタイムの実装によって決まります。最新のブラウザパフォーマンスの完全な比較については、JSPerfサイトにアクセスしてください。


関数本体の前の括弧を忘れました。私はちょうどそれをテストしました、彼らは必要です。
Chinoto Vokro 2014年

ベンチマークの結果は、jsエンジンに大きく依存しているようです。
aleclofabbro 2014

3
JS Perfの例に欠陥はありません。ケース1は関数を定義するだけですが、ケース2と3は誤って関数を呼び出しているようです。
bluenote10 2017年

したがって、この推論を使用すると、node.jsWebアプリケーションを開発するときに、匿名のコールバックを作成するよりも、要求フローの外部で関数を作成し、それらをコールバックとして渡す方がよいということですか?
Xavier T Mukodi

23

これが私のテストコードです:

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));
}

結果:
テスト1:142msテスト2:1983ms

JSエンジンは、Test2の同じ関数であることを認識せず、毎回コンパイルしているようです。


3
このテストはどのブラウザで実行されましたか?
andynil 2010年

5
Chrome 23での時間:(2ms / 17ms)、IE9:(20ms / 83ms)、FF 17:(2ms / 96ms)
Davy8 2012

あなたの答えはもっと重要な価値があります。Intel i5 4570Sでの私の時間:Chrome 41(1/9)、IE11(1/25)、FF36(1/14)。明らかに、ループ内の無名関数のパフォーマンスは低下します。
thisClark 2015年

3
このテストは、見た目ほど役に立ちません。どちらの例でも、内部関数は実際に実行されていません。事実上、このテストが示しているのは、関数を1回作成するよりも10000000回作成する方が速いということです。
核子2015

2

一般的な設計原則として、同じコードを複数回実装することは避けてください。代わりに、一般的なコードを関数に取り出し、その(一般的で、十分にテストされ、変更しやすい)関数を複数の場所から実行する必要があります。

(質問から推測したものとは異なり)内部関数を1回宣言し、そのコードを1回使用している場合(そしてプログラム内で他に同じものがない場合)、おそらく(推測では)匿名関数は同じように扱われます。通常の名前付き関数としてのコンパイラ。

これは特定のインスタンスで非常に便利な機能ですが、多くの状況で使用するべきではありません。


1

大きな違いはないと思いますが、違いがある場合は、スクリプトエンジンやブラウザによって異なる可能性があります。

コードを簡単に理解できる場合は、関数を何百万回も呼び出すことを期待しない限り、パフォーマンスは問題になりません。


1

パフォーマンスに影響を与える可能性があるのは、関数を宣言する操作です。これは、別の関数のコンテキスト内または外部で関数を宣言するためのベンチマークです。

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

Chromeでは、関数を外部で宣言すると操作が速くなりますが、Firefoxではその逆です。

他の例では、内部関数が純粋関数でない場合、Firefoxでもパフォーマンスが低下することがわかります:http//jsperf.com/function-context-benchmark-3


0

さまざまなブラウザー、特にIEブラウザー間でループを確実に高速化するのは、次のようにループすることです。

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

任意の1000をループ条件に入れましたが、配列内のすべての項目を調べたい場合は、私のドリフトが発生します。


0

参照は、ほとんどの場合、参照しているものよりも遅くなります。このように考えてください。たとえば、1 +1を加算した結果を出力するとします。これはより理にかなっています。

alert(1 + 1);

または

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

それは本当に単純な見方だと思いますが、それは実例ですよね?参照は、複数回使用される場合にのみ使用してください。たとえば、次の例のどれがより理にかなっていますか。

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

または

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

2つ目は、行数が多い場合でも、より良い方法です。うまくいけば、これがすべて役立つでしょう。(そしてjquery構文は誰も捨てませんでした)


0

@nickf

(コメントするだけの担当者がいたらいいのですが、このサイトを見つけたばかりです)

私のポイントは、名前付き/無名関数と、反復で実行+コンパイルするユースケースとの間に混乱があるということです。私が説明したように、anon + namedの違い自体はごくわずかです-私はそれが欠陥のあるユースケースだと言っています。

私には明らかなようですが、そうでない場合は、「愚かなことをしない」(このユースケースの一定のブロックシフト+オブジェクト作成が1つです)と確信が持てない場合はテストしてください。



0

匿名オブジェクトは、名前付きオブジェクトよりも高速です。ただし、より多くの関数を呼び出すとコストが高くなり、匿名関数を使用することで得られる節約を上回る程度になります。呼び出される各関数は、呼び出しスタックに追加されます。これにより、わずかですが重要なオーバーヘッドが発生します。

ただし、暗号化/復号化ルーチンなど、パフォーマンスに同様に敏感なものを作成している場合を除き、他の多くの人が指摘しているように、高速コードよりもエレガントで読みやすいコードに最適化する方が常に優れています。

適切に設計されたコードを作成していると仮定すると、速度の問題は、インタープリター/コンパイラーを作成する人の責任である必要があります。


0

@nickf

ただし、これはかなり骨の折れるテストですが、実行時間とコンパイル時間を比較しているため、メソッド1(N回コンパイル、JSエンジンによって異なります)とメソッド2(1回コンパイル)のコストが明らかにかかります。このような方法でコードを記述してプロベーションを渡すJS開発者を想像することはできません。

はるかに現実的なアプローチは匿名割り当てです。実際、document.onclickメソッドに使用しているのは次のようなもので、実際にはanonメソッドをやや支持しています。

あなたと同様のテストフレームワークを使用する:


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

@nickfへのコメントで指摘されているように答え:への答え

関数を100万回作成するよりも1回速く作成しています

単にそうです。しかし、彼のJSパフォーマンスが示すように、100万倍遅くなることはなく、時間の経過とともに実際に速くなることを示しています。

私にとってもっと興味深い質問は次のとおりです。

繰り返し作成+実行は、1回+繰り返し実行と比較してどうですか。

関数が複雑な計算を実行する場合、関数オブジェクトを作成する時間はほとんど無視できます。しかし、実行が速い場合の作成のオーバーヘッドはどうですか?例えば:

// 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);
}

このJSパフォーマンスは、関数の作成が予想どおりに1回だけ速いことを示しています。ただし、単純な追加のような非常に迅速な操作でも、関数を繰り返し作成するオーバーヘッドはわずか数パーセントです。

違いはおそらく、関数本体全体がにラップされている場合など、無視できる実行時間を維持しながら、関数オブジェクトの作成が複雑な場合にのみ重要になりますif (unlikelyCondition) { ... }

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.