web-dev-qa-db-ja.com

JavaScriptオブジェクトとマップのパフォーマンス(Chrome、V8、Node JS)

大規模なデータセット(> 1000オブジェクト)で文字列キーによるランダムルックアップが必要な場合に、JSオブジェクトまたはマップのどちらを使用するかを理解しようとしています。

私は簡単なベンチマークを作成しました http://jsperf.com/javascript-objects-vs-map-performance と結果はChrome(V8)オブジェクトが優れていることを示しています約2回マップしましたが、他のブラウザーをチェックしたところ、結果が逆になりましたが、ブラウザー/エンジンによって異なるのはなぜですか?

Node.JSでも同様のテストを作成しましたが、同様の結果が表示されません(テストケース6はテストケース4よりもはるかに多くかかりました)。

テスト

var now = require("performance-now");

var mapKeyValue = new Map();
var mapStringKeyValue = new Map();
var objectKeyValue = {};
var n = 10000;
var testSamples = 100;

var firstRow = 0;
var firstRowString = firstRow + "";

var middleRow = Math.floor(n / 2);
var middleRowString = middleRow + "";

var lastRow = n - 1;
var lastRowString = lastRow + "";

var nonExist = n * 2;
var nonExistString = nonExist + "";

function makeid() {
  var text = "";
  var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

  for (var i = 0; i < 20; i++)
    text += possible.charAt(Math.floor(Math.random() * possible.length));

  return text;
}

for (var i = 0; i < n; i++) {
  var value = makeid();
  mapKeyValue.set(i, value);
  mapStringKeyValue.set(i + "", value);
  objectKeyValue[i + ""] = value;
}

var t0, t1;

var averages = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

for (var j = 0; j < testSamples; j++) {
  var k = 0;
  t0 = now();
  mapKeyValue.get(firstRow);
  t1 = now();
  averages[k++] += (t1 - t0);

  t0 = now();
  mapStringKeyValue.get(firstRowString);
  t1 = now();
  averages[k++] += (t1 - t0);

  t0 = now();
  objectKeyValue[firstRowString];
  t1 = now();
  averages[k++] += (t1 - t0);


  t0 = now();
  mapKeyValue.get(middleRow);
  t1 = now();
  averages[k++] += (t1 - t0);

  t0 = now();
  mapStringKeyValue.get(middleRowString);
  t1 = now();
  averages[k++] += (t1 - t0);

  t0 = now();
  objectKeyValue[middleRowString];
  t1 = now();
  averages[k++] += (t1 - t0);


  t0 = now();
  mapKeyValue.get(lastRow);
  t1 = now();
  averages[k++] += (t1 - t0);


  t0 = now();
  mapStringKeyValue.get(lastRowString);
  t1 = now();
  averages[k++] += (t1 - t0);

  t0 = now();
  objectKeyValue[lastRowString];
  t1 = now();
  averages[k++] += (t1 - t0);


  t0 = now();
  mapKeyValue.get(nonExist);
  t1 = now();
  averages[k++] += (t1 - t0);

  t0 = now();
  mapStringKeyValue.get(nonExistString);
  t1 = now();
  averages[k++] += (t1 - t0);

  t0 = now();
  objectKeyValue[nonExistString];
  t1 = now();
  averages[k++] += (t1 - t0);
}

console.log("Test samples number " + testSamples);

for (var i = 0; i < averages.length; i++) {
  averages[i] /= testSamples;
  console.log("Test case " + (i + 1) + " took in average " + (averages[i] * 1000000) + " ns");
}

結果

Test samples number 100
Test case 1 took in average 2050.269999999692 ns
Test case 2 took in average 751.2899999997202 ns
Test case 3 took in average 567.3000000004081 ns
Test case 4 took in average 727.2699999999688 ns
Test case 5 took in average 4760.029999999489 ns
Test case 6 took in average 1939.3400000004135 ns
Test case 7 took in average 673.549999999885 ns
Test case 8 took in average 689.3600000002564 ns
Test case 9 took in average 541.3700000001143 ns
Test case 10 took in average 1146.0599999999843 ns
Test case 11 took in average 3096.7699999998285 ns
Test case 12 took in average 644.7400000000058 ns

ベンチマークを改善し、より正確にする方法についてアイデアがあれば教えてください。ありがとうございました。

22
Andrew Marin

最長のテストケースの所要時間は0.005ミリ秒未満でした。コンピュータが朝食にそれを食べています。あなたは、コールドブート時間の微視的な変動が結果を相対パーセンテージとして劇的に変化させる領域にいます。そのため、これらの数値はあまり意味がありません。

読みやすさなどの問題を考慮して最適化することをお勧めします。 機能させる、適切にする、高速にする 。 Mapは新しく、エンジンが進化するにつれて速くなることを覚えておいてください。

以下は、マイクロベンチマーク、落とし穴、およびそれらを改善する方法に関連するリソースです。

10
Seth Holladay

私は同様の質問をしてテストケースを書きました、そして最初の答えはあなたの質問と似ていましたが、どちらも現代のJSエンジンは関数の結果に関係のないコードを除去する能力があるとは考えていませんでした。

JSエンジンがテストケースを完全に削除できたため、テストケースが誤解を招く結果を示しているため、エンジンが空のループを実行できる速度を測定しました。

ブラウザがコードを削除する機会がないことを確認する新しいテストケースを作成しました。結果は、マップが関連オブジェクトのほぼ2倍の速さであることを示しています。 https://jsperf.com/map- vs-object-vs-frozen

performance test results

このテストには、実際にMapオブジェクトを初期化するためのコストが含まれていないことに注意してください。したがって実際には、小さなコードスニペットに対してローカルオブジェクトを使用する方が高速である可能性が高く、実際のマップは、グローバルコンテキストで大量のデータを保存する場合にのみ高速になります。

興味深いのは、ブラウザがオブジェクトに書き込み操作がないことを認識しているため、そうでなければ実行する必要があるすべての更新チェックを無視することです。したがって、凍結されたパフォーマンスは実際には遅くなりますが、高速になると予想されます。

6
TwoThe