提问者:小点点

Javascript 对象与映射性能(Chrome、V8、Node JS)


如果我需要在大型数据集中按字符串键随机查找,我试图了解是使用JS Object还是Map(

我写了一个简单的基准测试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

如果您对如何改进基准并使其更准确有任何想法,请告诉我。谢谢。


共2个答案

匿名用户

我刚刚有一个类似的问题并编写了一个测试用例,第一个答案与您的答案相似,但是我们都认为现代JS引擎在消除与函数结果无关的代码方面非常有能力。

这意味着您的测试用例向您显示了误导性的结果,因为JS-engine能够完全删除您的测试用例,因此您测量了引擎运行空循环的速度。

我编写了一个新的测试用例,确保浏览器没有机会消除代码,结果显示地图的速度几乎是关联对象的两倍:https://jsperf.com/map-vs-object-vs-frozen

请注意,此测试不包括实际初始化Map对象的成本。因此,在现实中,将本地对象用于小代码片段很可能更快,其中实际的Map仅在您在全局上下文中存储大量数据的情况下才会更快。

有趣的是,浏览器意识到对象上没有写操作,因此忽略了所有更新检查。因此,冻结的性能实际上更慢,而人们期望它更快。

匿名用户

您最长的测试用例用时不到0.005毫秒。电脑正在把它当早餐吃。在你所处的领域中,冷启动时间的微小波动会以相对百分比的形式极大地改变结果。这使得这些数字意义不大。

我建议针对可读性和其他问题进行优化。让它工作,让它正确,然后让它变得快速。请记住,地图是新的,随着引擎的发展会变得更快。

以下是一些与微观基准、陷阱和改进方法相关的资源。

  • https://www.youtube.com/watch?v=g0ek4vV7nEA
  • https://www.sitepoint.com/measuring-javascript-functions-performance/