web-dev-qa-db-ja.com

lodash.eachがネイティブのforEachより速いのはなぜですか?

独自のスコープでforループを実行する最速の方法を見つけようとしていました。比較した3つの方法は次のとおりです。

var a = "t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t".split();

// lodash .each -> 1,294,971 ops/sec
lodash.each(a, function(item) { cb(item); });

// native .forEach -> 398,167 ops/sec
a.forEach(function(item) { cb(item); });

// native for -> 1,140,382 ops/sec
var lambda = function(item) { cb(item); };
for (var ix = 0, len = a.length; ix < len; ix++) {
  lambda(a[ix]);
}

これはOS XのChrome 29にあります。ここでテストを実行できます。

http://jsben.ch/BQhED

Lodashの.eachは、ネイティブ.forEachのほぼ2倍の速度ですか?さらに、プレーンforよりも高速です。魔術?黒魔術?

64
Matt Zukowski

_.each()[].forEach()と完全に互換性がありません。次の例を参照してください。

var a = ['a0'];
a[3] = 'a3';
_.each(a, console.log); // runs 4 times
a.forEach(console.log); // runs twice -- that's just how [].forEach() is specified

http://jsfiddle.net/BhrT3/

そのため、lodashの実装にはif (... in ...)チェックがありません。これはパフォーマンスの違いを説明する可能性があります。


上記のコメントで述べたように、ネイティブforとの違いは、主にテストでの追加の関数ルックアップが原因です。このバージョンを使用して、より正確な結果を取得します。

for (var ix = 0, len = a.length; ix < len; ix++) {
  cb(a[ix]);
}

http://jsperf.com/lo-dash-each-vs-native-foreach/15

87

http://kitcambridge.be/blog/say-hello-to-lo-dash/

Lo-dash開発者は、ネイティブforEachの相対的な速度がブラウザによって異なることを説明しています(こことビデオで)。 forEachがネイティブだからといって、forまたはwhileで構築された単純なループよりも高速であることを意味しません。一つには、forEachはより特殊なケースに対処する必要があります。第二に、forEachはコールバックを使用し、関数呼び出しなどの(潜在的な)オーバーヘッドを伴います。

chromeは、特に(少なくともlo-dash開発者には)比較的遅いforEachを持つことが知られています。そのため、そのブラウザでは、lo-dashは独自の単純なwhileループを使用して速度を上げます。したがって、表示される速度の利点(ただし、他の人には表示されません)。

特定の環境で高速であることがわかっている場合にのみネイティブ実装を使用してネイティブメソッドを賢く選択することにより、Lo-Dashはネイティブに関連するパフォーマンスコストと一貫性の問題を回避します。

23
hpaulj

はい、lodash/underscoreはそれぞれ.forEachと同じセマンティクスを持っていません。エンジンがゲッターなしでスパース配列を迅速にチェックできない限り、関数を本当に遅くする微妙な詳細があります。

これは99%仕様に準拠しており、 V8でそれぞれlodashと同じ速度で実行されます 一般的な場合:

function FastAlmostSpecForEach( fn, ctx ) {
    "use strict";
    if( arguments.length > 1 ) return slowCaseForEach();
    if( typeof this !== "object" ) return slowCaseForEach();
    if( this === null ) throw new Error("this is null or not defined");
    if( typeof fn !== "function" ) throw new Error("is not a function");
    var len = this.length;
    if( ( len >>> 0 ) !== len ) return slowCaseForEach();


    for( var i = 0; i < len; ++i ) {
        var item = this[i];
        //Semantics are not exactly the same,
        //Fully spec compliant will not invoke getters
       //but this will.. however that is an insane Edge case
        if( item === void 0 && !(i in this) ) {
            continue;
        }
        fn( item, i, this );
    }
}

Array.prototype.fastSpecForEach = FastAlmostSpecForEach;

最初に未定義をチェックすることにより、ループ内の通常の配列を罰しません。エンジンは内部を使用して奇妙な配列を検出できますが、V8は検出しません。

16
Esailija

for(...)Array.forEach、および_.eachの3つすべてを比較するパフォーマンスの違いを示す更新されたリンク(2015年頃)は次のとおりです。 https://jsperf.com/native-vs -underscore-vs-lodash

注:受け入れられた答えにコメントするのに十分なポイントがまだなかったので、ここに入れてください。

3
chunk_split