web-dev-qa-db-ja.com

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

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

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

function myEventHandler() {
    // do something
}

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

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

83
nickf

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

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

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

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関数宣言のあいまいさ 」を読むことをお勧めします。

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

83
Atif Aziz

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

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の同じ関数であることを認識せず、毎回コンパイルしているようです。

21
nickf

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

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

これは特定の場合に非常に役立つ機能ですが、多くの状況では使用しないでください。

2
Tom Leys

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

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

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

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

1
Pablo Estornut

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

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

1
Joe Skora

匿名オブジェクトは、名前付きオブジェクトよりも高速です。しかし、より多くの関数を呼び出すと、よりコストがかかり、匿名関数を使用することで得られる可能性のある節約の一部を超えることになります。呼び出された各関数はコールスタックに追加されます。これにより、小さいながらも重要なオーバーヘッドが発生します。

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

あなたがよく設計されたコードを書いていると仮定すると、速度の問題はインタプリタ/コンパイラを書く人の責任であるべきです。

1
pcorcoran

@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
annakata

@nickf

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

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

私には自明のようですが、最善のアドバイスは「馬鹿げたことをしないでください」(このユースケースのブロックシフト+オブジェクトの作成は常に1つです)であり、確信がない場合はテストしてください。

0
annakata

はい!無名関数は、通常の関数よりも高速です。おそらく、速度が最も重要な場合は...コードの再利用よりも重要であり、匿名関数の使用を検討してください。

JavaScriptと無名関数の最適化に関する本当に良い記事がここにあります:

http://dev.opera.com/articles/view/efficient-javascript/?page=2

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
matt lohkamp

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

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

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

私にとってもっと興味深い質問は:

繰り返し作成+実行を比較して1回作成+繰り返し実行を行う方法。

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

_// 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 Perf は、関数を1回だけ作成する方が期待どおりに高速であることを示しています。ただし、単純な追加のような非常に迅速な操作でも、関数を繰り返し作成するオーバーヘッドは数パーセントにすぎません。

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

0
bluenote10

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

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

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

0
Sarhanis