web-dev-qa-db-ja.com

jQuery.grepとArray.filterのパフォーマンス

質問 では、jQueryとネイティブJSが互いにどのように機能するかについて説明しました。

もちろん、Vanillaソリューションはアレイ全体を処理しないため、はるかに高速に実行できますが、Array.filter私はかなり自信があると少なくとも$.grep

驚いたことに、テストに追加した後、レッスンを教えられました:Testsuite

エッジケース もちろん異なる結果になります。

_$.grepは、ネイティブメソッドの3倍以上高速になるはずですArrray.filter

編集: MDNからのフィルターシム を使用するようにテストを変更し、結果はかなり興味深いものになりました。

  • Chrome:MDN shimでさえネイティブメソッドよりも高速で、jQueryの先を行く
  • Firefox:ネイティブメソッドよりも少し遅く、jQueryが先

そして、最終的に私がそれを見てみたいと思っていたような結果

  • Internet Explorer:ネイティブメソッドが最も速く、次にjQuery、shimが最も遅い(おそらく、これはIEではなく、弱いJSエンジンの結果でしょう...)
36
Christoph

このブログ記事(同じ種類のテストも行います)にあります:

filter のドキュメントを読むと、なぜこれほど遅いのかがわかります。

  1. 削除された値と配列内のギャップを無視します
  2. オプションで、述語関数の実行コンテキストを設定します
  3. 述語関数がデータを変更するのを防ぎます
14
MarcoK

ECMAScript 5.1仕様のセクション15.4.4.2 は、次のようにArray.prototype.filter(callbackfn, thisArg)を定義します。

callbackfnは、3つの引数を受け入れ、ブール値trueまたはfalseに強制可能な値を返す関数でなければなりません。 filterは、配列の各要素に対して昇順でcallbackfnを1回呼び出し、callbackfntrueを返すすべての値の新しい配列を作成します。 callbackfnは、実際に存在する配列の要素に対してのみ呼び出されます。配列の欠落要素については呼び出されません。

thisArgパラメーターが指定されている場合、thisの呼び出しごとにcallbackfn値として使用されます。指定されていない場合は、代わりにundefinedが使用されます。

callbackfnは、要素の値、要素のインデックス、およびトラバースされるオブジェクトの3つの引数で呼び出されます。

filterは、呼び出されるオブジェクトを直接変更しませんが、callbackfnの呼び出しによってオブジェクトが変更される場合があります。

Filterによって処理される要素の範囲は、callbackfnの最初の呼び出しの前に設定されます。 filterの呼び出しが開始された後に配列に追加される要素には、callbackfnがアクセスしません。配列の既存の要素が変更された場合、callbackfnに渡される値は、フィルターがそれらを訪問したときの値になります。 filterの呼び出しが開始されてからアクセスされる前に削除される要素はアクセスされません。

それ自体はすでに多くの作業です。 ECMAScriptエンジンが実行する必要がある多くのステップ。

次に、次のことを言い続けます。

1つまたは2つの引数を使用してフィルターメソッドが呼び出されると、次の手順が実行されます。

OToObjectを引数として渡してthisを呼び出した結果とします。 lenValueを、Oの_[[Get]]_内部メソッドを引数lengthで呼び出した結果とします。 lenToUint32(lenValue)とする。 IsCallable(callbackfn)がfalseの場合、TypeError例外をスローします。 thisArgが指定された場合、TをthisArgにします。それ以外の場合は、Tを未定義にします。 Aを式new Array()によって作成された新しい配列とします。ここで、Arrayはその名前を持つ標準の組み込みコンストラクタです。 kを0にします。0にします。k<lenの間、繰り返します。PkをToString(k)にします。 kPresentを、引数Pkを使用してOの[[HasProperty]]内部メソッドを呼び出した結果とします。 kPresentがtrueの場合、kValueを引数PkでOの[[Get]]内部メソッドを呼び出した結果とします。 selectedを、コールバックfnの[[Call]]内部メソッドをTとしてthis値と、kValue、k、およびOを含む引数リストで呼び出した結果とします。ToBoolean(selected)がtrueの場合、[[DefineOwnProperty]]引数ToString(to)、プロパティ記述子{[[Value]]:kValue、[[Writable]]:true、[[Enumerable]]:true、[[Configurable]]:true}、およびfalseを持つAの内部メソッド。に1を増やします。kを1増やします。Aを返します。

フィルターメソッドの長さプロパティは1です。

注フィルター機能は意図的に汎用です。 this値がArrayオブジェクトである必要はありません。したがって、メソッドとして使用するために他の種類のオブジェクトに転送できます。フィルター関数をHostオブジェクトに正常に適用できるかどうかは、実装に依存します。

このアルゴリズムに関する注意事項:

  • 述語関数がデータを変更するのを防ぎます
  • オプションで、述語関数の実行コンテキストを設定します
  • 削除された値と配列内のギャップを無視します

多くの場合、これらはどれも必要ありません。そのため、独自のfilterメソッドを記述する場合、ほとんどの場合、これらの手順を実行する手間さえかかりません。

ES5.1準拠のJavaScriptエンジンはすべて、そのアルゴリズムに準拠する必要があるため、_Array#filter_を使用するたびにこれらすべての手順を実行する必要があります。

これらの手順の一部のみを実行するカスタム作成メソッドが高速になることは驚くべきことではありません:)

独自のfilter関数を作成する場合、上記のアルゴリズムほど複雑ではない可能性があります。おそらく、配列をオブジェクトに変換することはまったくありません。ユースケースによっては、配列をフィルタリングするだけでは必要ない場合があるためです。

8
Mathias Bynens

面白いことがわかりました。 MarcoKで説明したように、$。grepはforループを使用した単純な実装です。ほとんどの場合、フィルターは低速であるため、実装は異なる必要があります。私は答えを見つけたと思います:

function seak (e) { return e === 3; }

var array = [1,2,3,4,5,6,7,8,9,0], i, before;
array[10000] = 20; // This makes it slow, $.grep now has to iterate 10000 times.
before = new Date();

// Perform natively a couple of times.
for(i=0;i<10000;i++){
    array.filter(seak);
}

document.write('<div>took: ' + (new Date() - before) + '</div>'); // took: 8515 ms (8s)

before = new Date();

// Perform with JQuery a couple of times
for(i=0;i<10000;i++){
    $.grep(array, seak);
}
document.write('<div>took: ' + (new Date() - before) + '</div>'); // took: 51790 ms  (51s)

この場合、ネイティブの「フィルター」ははるかに高速です。したがって、配列インデックスではなくプロパティを反復処理すると思います。

ここで、「大きな」問題に戻りましょう;)。

3
nicojs

スクリプトは間違っていませんか?

にとって array.filter 1000回測定を行っており、合計を1000で除算して表示します。

にとって JQuery.grep 1回測定を行っており、合計を1000で割って表示します。

これは、grepが実際に比較に使用する値の1000倍遅いことを意味します。

Firefoxのクイックテストは以下を提供します。

Machine 1:
average filter - 3.864
average grep - 4.472

Machine2:
average filter - 1.086
average grep - 1.314

chromeでのクイックテストは以下を提供します:

Machine 1:
average filter - 69.095
average grep - 34.077

Machine2:
average filter - 18.726
average grep - 9.163

Firefox(50.0)の結論は、コードパスに対して非常に高速であり、フィルターはjquery.grepよりも約10〜15%高速です。

Chromeはコードパスに対して非常に遅いですが、grepはarray.filterよりも50%高速であるようで、firefoxの実行よりも900%遅くなっています。

2
Riek Steenwegen