web-dev-qa-db-ja.com

継承されたプロパティのないfor-in vs Object.keys forEach

私は通常のオブジェクトでObject.keys + forEachfor-inのパフォーマンスベンチマークを見ていました。

これ ベンチマークは、Object.keys + forEach62%遅いfor-inアプローチよりも遅いことを示しています。しかし、継承されたプロパティを取得したくない場合はどうでしょうか? for-inには、ネイティブでない継承オブジェクトがすべて含まれているため、hasOwnPropertyを使用して確認する必要があります。

私は別の ベンチマークはこちら を作ってみました。しかし、今、for-inアプローチは、Object.keys + forEachより41%遅いです。


更新

上記のテストはChromeで行われました。もう一度テストしましたが、Safariとは異なる結果が得られます:Object.keys(..).forEach(..) 34% slower、奇数。

注:私がベンチマークを行っている理由は、それがNode.jsでどのようになっているかを確認するためです。

質問:

  • Node.jsChromejsperf結果は重要ですか?
  • Chromefor-in + forEachよりも単一の条件付きでObject.keysアプローチ41%遅いになったのはなぜですか?
31
majidarif

node.jsはV8を使用しますが、Chromeの現在のバージョンと同じではないと思いますが、これはノードに関するノードのパフォーマンスの良い指標になると思います。

次に、forEachを使用しています。これは開発時に非常に便利ですが、反復ごとにコールバックを追加します。これは(比較的)長いタスクです。では、パフォーマンスに興味がある場合は、通常のforループを使用してみませんか?

for (var i = 0, keys = Object.keys(object); i < keys.length; i++) {
    // ...
}

これにより、最高のパフォーマンスが得られ、Safariでの速度の問題も解決されます。

つまり、条件付きではなく、違いはhasOwnPropertyへの呼び出しです。すべての反復で関数呼び出しを行っているので、for...inが遅くなります。

30
MaxArt

ただそれに注意してください:

var keys = Object.keys(obj), i = keys.length;

while(--i) {
   //
}

インデックス0では実行されないため、属性の1つを見逃すことになります。

["a"、 "b"、 "c"、 "d"]のような配列では、d、c、bのみが実行され、インデックスが0で0がfalseであるため、 "a"を見逃します。

Whileチェックの後、デクリメントする必要があります。

var keys = Object.keys(obj), i = keys.length;

while(i--) {
   //
}
17
Jean Robert

今日もこれに興味がありましたが、ほとんどの場合、オブジェクトがクリーンであることをすでに知っているときに(オブジェクトリテラルから作成されているため)、hasOwnPropertyでテストしてデフォルトのlintを渡す必要がありません。とにかく、@ styonskの回答を少し拡張して、より優れた出力を含め、複数のテストを実行して出力を返すようにしました。

結論:ノードの場合は複雑です。最適なタイミングは、node.js v4.6.1で数値のforループまたはwhileループのいずれかでObject.keys()を使用するように見えます。 v4.6.1では、hasOwnPropertyを使用したforInループが最も遅いメソッドです。ただし、ノードv6.9.1では最も高速ですが、v4.6.1の両方のObject.keys()イテレータよりも低速です。

メモ:これは、2013年後半のMacBook Proで、16GBのRAMと2.4Ghz i5プロセッサーを搭載して実行されました。すべてのテストは、テストの期間中、単一のCPUの100%を固定し、平均rssは約500MBで、ピークは1GBのrssでした。これが誰かを助けることを願っています。

これは、大きなオブジェクト(10 ^ 6個のプロパティ)と小さなオブジェクト(50個のプロパティ)を使用してnodejs v6.9.1とv4.6.1に対して実行した私の結果です。

ラージオブジェクト10 ^ 6プロパティを持つノードv4.6.1

testObjKeyWhileDecrement テスト数: 100 合計時間: 57595 ms 平均時間: 575.95 ms

testObjKeyForLoop テスト数: 100 合計時間: 54885 ms 平均時間: 548.85 ms

testForInLoop テスト数: 100 合計時間: 86448 ms 平均時間: 864.48 ms

小さなオブジェクト50プロパティを持つノードv4.6.1

testObjKeyWhileDecrement テスト数: 1000 合計時間: 4 ms 平均時間: 0.004 ms

testObjKeyForLoop テスト数: 1000 合計時間: 4 ms 平均時間: 0.004 ms

testForInLoop テスト数: 1000 合計時間: 14 ms 平均時間: 0.014 ms

ラージオブジェクト10 ^ 6プロパティを持つノードv6.9.1

testObjKeyWhileDecrement テスト数: 100 合計時間: 94252 ms 平均時間: 942.52 ms

testObjKeyForLoop テスト数: 100 合計時間: 92342 ms 平均時間: 923.42 ms

testForInLoop テスト数: 100 合計時間: 72981 ms 平均時間: 729.81 ms

小さなオブジェクト50プロパティを持つノードv4.6.1

testObjKeyWhileDecrement テスト数: 1000 合計時間: 8 ms 平均時間: 0.008 ms

testObjKeyForLoop テスト数: 1000 合計時間: 10 ms 平均時間: 0.01 ms

testForInLoop テスト数: 1000 合計時間: 13 ms 平均時間: 0.013 ms

そして以下は私が実行したコードです:

//Helper functions
function work(value) {
  //do some work on this value
}

function createTestObj(count) {
  var obj = {}
  while (count--) {
    obj["key" + count] = "test";
  }
  return obj;
}

function runOnce(func, obj) {
  var start = Date.now();
  func(obj);
  return Date.now() - start;
}

function testTimer(name, func, obj, count) {
  count = count || 100;
  var times = [];
  var i = count;
  var total;
  var avg;

  while (i--) {
    times.Push(runOnce(func, obj));
  }

  total = times.reduce(function (a, b) { return a + b });
  avg = total / count;

  console.log(name);
  console.log('Test Count: ' + count);
  console.log('Total Time: ' + total);
  console.log('Average Time: ' + avg);
  console.log('');
}

//Tests
function testObjKeyWhileDecrement(obj) {
  var keys = Object.keys(obj);
  var i = keys.length;
  while (i--) {
    work(obj[keys[i]]);
  }
}

function testObjKeyForLoop(obj) {
  var keys = Object.keys(obj);
  var len = keys.length;
  var i;
  for (i = 0; i < len; i++) {
    work(obj[keys[i]]);
  }
}

function testForInLoop(obj) {
  for (key in obj) {
    if (obj.hasOwnProperty(key)) {
      work(obj[key]);
    }
  }
}

//Run the Tests
var data = createTestObj(50)
testTimer('testObjKeyWhileDecrement', testObjKeyWhileDecrement, data, 1000);
testTimer('testObjKeyForLoop', testObjKeyForLoop, data, 1000);
testTimer('testForInLoop', testForInLoop, data, 1000);
2
baetheus

JSでオブジェクトのプロパティを繰り返し処理することに関心がある場合、絶対的に最速の方法は次のとおりです。

var keys = Object.keys(obj), i = keys.length;

while(--i) {
   //
}

http://jsperf.com/object-keys-foreach-vs-for-in-hasownproperty/8

単純なforループの場合にも当てはまる、キー配列の長さの値(最新のブラウザー最適化ではほとんど無視できる)を再計算する必要がないため、大きなオブジェクトを少し節約できます。デクリメントされたwhileループは、forループや、長さの上限を比較したインクリメントされたwhileループよりもかなり高速です。

1
coldfire

今日、これをテストしました。私の目的では、オブジェクトキーを取得してから、単純な古いforループを実行する方が、whileまたはfor inループを実行するよりも高速でした。このテンプレートを自由に変更して、個別のケースでさまざまなループをテストしてください。

//Helper functions
function work(value) {
  //do some work on this value
}

function createTestObj(count) {
  var obj = {}
  while (count--) {
    obj["key" + count] = "test";
  }
  return obj;
}

//Tests
function test_ObjKeyWhileDecrement(obj) {
  console.log("Time Started: ", new Date().getTime());
  var keys = Object.keys(obj),
    i = keys.length;
  while (i--) {
    work(obj[keys[i]]);
  }
  console.log("Time Finished: ", new Date().getTime());
}

function test_ObjKeyForLoop(obj) {
  console.log("Time Started: ", new Date().getTime());
  for (var i = 0, keys = Object.keys(obj); i < keys.length; i++) {
    work(obj[keys[i]]);
  }
  console.log("Time Finished: ", new Date().getTime());
}

function test_ForInLoop(obj) {
  console.log("Time Started: ", new Date().getTime());
  for (key in obj) {
    work(obj[key]);
  }
  console.log("Time Finished: ", new Date().getTime());
}

//Run the Tests
var data = createTestObj(1000 * 100)
console.log("Test Obj Key While Decrement Loop")
test_ObjKeyWhileDecrement(data);
console.log("Test Obj Key For Loop")
test_ObjKeyForLoop(data);
console.log("Test For In Loop")
test_ForInLoop(data);

Jsfiddleではなく、実際の環境でテストを実行することをお勧めします。複数のブラウザも試してください。

0
styonsk

そしてES6のファンにとっては

Object.keys(obj).reduce((a,k) => {a += obj[k]; return a}, res)

断然最速です。

https://jsperf.com/for-in-vs-for-of-keys-vs-keys-reduce

0
Dallas