【问题标题】:Why do the following two pieces of code run so differently?为什么以下两段代码的运行方式如此不同?
【发布时间】:2021-02-16 11:48:54
【问题描述】:

看这两段代码,第二段只加了第三行。但是时间是84倍。谁能解释一下为什么?

let LIMIT = 9999999;
let arr = new Array(LIMIT);
// arr.push(1);
console.time('Array insertion time');
for (let i = 1; i < LIMIT; i++) {
  arr[i] = i;
}
console.timeEnd('Array insertion time');

let LIMIT = 9999999;
let arr = new Array(LIMIT);
arr.push(1);
console.time('Array insertion time');
for (let i = 1; i < LIMIT; i++) {
  arr[i] = i;
}
console.timeEnd('Array insertion time');

【问题讨论】:

标签: javascript node.js google-chrome v8


【解决方案1】:

arr.push(1) 操作创建一个“稀疏”数组:它在索引9999999 处有一个元素。 V8 将这种稀疏数组的内部表示切换为“字典模式”,即数组的后备存储是索引→元素字典,因为这比仅使用其中一个时为 1000 万个元素分配空间要显着提高内存效率。

另一方面,字典模式数组的访问(读取或写入)元素比“快速/密集模式”中的数组要慢:每次访问都必须计算正确的字典索引,并且(在场景中手)字典必须增长几次,这意味着将所有现有元素复制到新的后备存储中。

随着阵列被填满,V8 注意到它变得越来越密集,并在某个时候将其转换回“快速/密集模式”。到那时,已经观察到大部分放缓。循环的其余部分也增加了一些成本,因为此时,arr[i] = i; 存储已经看到了两种类型的数组(字典模式和密集模式),所以在每次迭代时,它必须检测数组处于哪个状态现在并相应地处理它,这(不足为奇)比不必做出决定要花费更多的时间。

概括的结论:由于 JavaScript 如此动态和灵活,引擎对于看起来非常相似的代码片段的行为可能会完全不同;例如,因为引擎优化了一种情况下的内存消耗和另一种情况下的执行速度,或者因为其中一种情况允许它使用一些不适用于另一种情况的快捷方式(无论出于何种原因)。好消息是,在许多情况下,正确且易于理解/直观/简单的代码也往往运行良好(在此示例中,流浪的 arr.push 看起来很像一个错误)。

【讨论】:

  • 非常感谢您的回复。我学到了很多。如果我将 arr.push(1) 替换为 arr[LIMIT] = 1,则代码运行缓慢。但是如果我把它换成 arr[LIMIT - 1] = 1,这个数组看起来也是“稀疏”数组,但它运行得很快。你能帮忙解释一下吗?期待您的回答。
  • arr[LIMIT-1] = 1 可以使用现有的、预分配的后备存储,因此不会触发切换到字典模式。
  • 当一个答案回答了您的问题时,StackOverflow 表示感谢的方式是将其标记为已接受,并可选择对其进行投票:-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-26
  • 2023-03-18
  • 2021-10-14
  • 1970-01-01
  • 2017-01-03
  • 1970-01-01
相关资源
最近更新 更多