为什么小型阵列的for-of循环比标准循环快,而大型阵列的循环慢?

时间:2018-10-22 03:33:49

标签: javascript arrays performance

在JavaScript中,我注意到ES6 for ... of循环的性能与传统的for (start; stop; step)循环大不相同。

基准

const n = 10000;
const arr = Array(n).fill().map((e, i) => i); // [0, n)

console.log('n =', n);

let sum1 = 0;
console.time('for let i');
for (let i = 0; i < arr.length; i++) {
  sum1 += arr[i];
}
console.timeEnd('for let i');

let sum2 = 0;
console.time('for of');
for (let v of arr) {
  sum2 += v;
}
console.timeEnd('for of');

结果

n = 10
for let i: 0.350ms
for of: 0.015ms
-----
n = 100
for let i: 0.354ms
for of: 0.023ms
-----
n = 1000
for let i: 0.429ms
for of: 0.111ms
-----
n = 10000
for let i: 1.048ms
for of: 2.138ms
-----
n = 100000
for let i: 9.452ms
for of: 13.644ms

(使用Node.js v10.11.0测试)

如您所见,随着n的增加,for-of循环的速度以比标准for循环更快的速度降低。为什么for-of循环对于较小的数组更快,而对于较大的数组则慢?

4 个答案:

答案 0 :(得分:7)

当对较小的值进行基准测试时,开销操作可能会对测试产生更大的影响。

例如,如果变量初始化和内存分配花费0.1毫秒,则在n> 1000时可以忽略不计,但在n = 10时有效。

在这种情况下,for / of运算符允许V8引擎优化循环操作(减少上述开销)。例如,它可以将数组项预加载到堆栈或类似对象上。

for / let操作将独立于整个数组处理每个项目,并且在变量用法上更加明确(减少引擎可以执行的优化量)。

答案 1 :(得分:5)

我建议您研究一下microbenchmarking的概念,并熟悉this great answer在JS中的微基准测试。

简而言之,在测试诸如for循环之类的小东西时,您很容易使测试逻辑受到other ongoing processes under the hood的干扰。例如,如果在测试用例中交换执行顺序,以使for offor let之前执行,您可能会注意到较小的n值的时间变化令人惊讶( spoiler:在这种情况下,for let赢得了n=10n=100 )的比赛

因此,您的“为什么”问题的答案:for let在列表中的速度较慢,因为它离程序启动更近,并且在更多的“冷”虚拟机上执行,也为随后的{{1 }}语句。 for let越大,这种副作用对测得的执行时间的贡献就越小。

这就是为什么微基准测试意味着要执行一系列相同的测试,而不是仅仅执行一个测试-在较小的规模上产生小的副作用就不那么重要了

此外,请注意,语句性能的标准度量单位是“每单位时间的总操作数”,而不是每组操作的绝对时间

可以找到您的主题的示例微基准测试here

答案 2 :(得分:3)

for-of仅在数据(像数组的单元格)上循环 ,而不是对象本身。大大提高了性能。

另一个循环遍历对象。 for-of循环使内部代码更简单。

而不是访问数组的内存地址(十六进制数字),对于n大小的较大数组,这需要更长的时间。这些类型的循环更适合少量。

for-of循环迭代数字 data 而不是对象,这在循环大量元素时效率更高。您将必要的循环类型应用于代码的特定情况。

这就像一匹马和一辆汽车。这匹马适合少于100英里而不是100英里以上的旅程。当汽车可以做得更好时。


有关更多信息,请参考此链接。 -https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/

答案 3 :(得分:0)

让我尝试解释一下,在这种情况下 for for ... of 快,因为操作简单得多,详细说明可以找到here

  

(var VariableDeclarationList ; Expression ; Expression 声明

for (let i = 0; i < arr.length; i++) {
  sum1 += arr[i];
}

那么这是怎么回事:

  • 检查我
  • 我加一个

但是

有些不同
  

表达式中的 ForBinding 声明

let sum2 = 0;
console.time('for of');
for (let v of arr) {
  sum2 += v;
}

据我所知, for..of 使用特定于对象的迭代器并循环其生成的值,因此这将比 for 花费更多时间。这里没什么有趣的。如果您想了解更多有关此内容的信息,请检查运行时语义上的差异:

用于运行时语义:here

用于...的运行时语义:here

这种情况仅是有效的用户定义的Symbol.iterators,但应优化内置迭代器以使其在 for ... of (而不是 for )中更好地工作>“ 感谢@Aryter

那么您的测试为什么不准确?因为您在 for 循环后生成了 for ... of ,所以V8 engine正在此处进行一些优化。

我认为优化与JavaScript属性有关,并且在对象较小时会产生明显的速度差异V8引擎,有关详细信息,请参见V8 Engine fast properties

希望现在更加清晰了...