数组的forEach比本地迭代运行更快?为什么?

9

http://jsperf.com/testing-foreach-vs-for-loop

据我了解,测试用例2应该比测试用例1运行更慢-- 我想看看它慢多少。但让我惊讶的是,它运行得更快了!

这是怎么回事?背后的优化原因?还是 .forEach 更简洁,更快?

在 Windows Server 2008 R2 / 7 64-bit 上使用 Chrome 18.0.1025.142 32-bit 进行测试。


1
forEach 可能具有一些本地优化,或者其他什么东西。 - gen_Eric
3
此处代码第一测试用例中,您甚至没有从数组中获取值,而是记录了变量i,而不是记录array[i]的值。请修改代码。 - huysentruitw
  1. 您不应该在每个测试中都创建数组,您可以定义变量,这些变量将在所有测试中都可访问。
  2. 您正在每次循环中检查“ .length”。
- ajax333221
如果你运行版本为7的 http://jsperf.com/testing-foreach-vs-for-loop/7 ,结果会很有趣,并且这些评论中提到的问题已经得到了纠正。 - kiswa
5个回答

11

您的for循环缺少许多迭代优化,例如:

  • 缓存数组长度
  • 反向迭代
  • 使用++counter而不是counter++

这些是我听说和使用过的,我确信还有更多。如果我没记错,反向迭代while循环是所有循环结构中最快的(在大多数浏览器中)。

请参见此jsperf获取一些示例。

编辑: 后缀与前缀性能测试反向迭代的链接。 我找不到使用+=1代替++的参考资料,因此我已将其从列表中删除。


在JS中,反向迭代并使用+=1而不是++实际上没有任何变化。 - kirilloid
1
@kirilloid += 和 ++ 是两个完全不同的运算符,在不同的解析器实现中可能以不同的速度运行,而递减与递增是一个众所周知的微观优化。我会编辑并添加参考资料。 - jbabey
@ajax333221 这个SO问题指出前缀形式(++i)比后缀形式(i++)更快。但它是否在特定浏览器中更快是另一个问题... - jbabey
@ErikReppen 避免 off-by-one 错误是一个不同于循环性能和优化的话题。真正的问题在于这个人是否想要为了性能而牺牲可读性和可维护性。 - jbabey
我没有解释得很清楚。i--的性能优势在于您可以通过while(i--)在while循环中进行评估,而不是(i <任何逻辑运算符> someValue)。我添加了有关i--与--i的详细信息,以解释其工作原理。我还给出了一个错误的for循环示例(错误在于我忘记了在for循环中评估和递减不会立即发生),这就是为什么我删除了该注释的原因。 - Erik Reppen
显示剩余6条评论

3

更新:

很多旧技巧在老浏览器中的解释JS非常好。

在任何现代JS实现中,包括所有现代浏览器、Node和最新的移动Webviews,内联函数实际上可以被JIT(JS编译器)缓存,使得forEach成为数组迭代的更快选项。以前情况相反,仅重复调用函数需要建立/拆除过程,这可能会严重降低非平凡循环的性能。

为了获得最佳性能,如果不必要,我建议避免引用任何未传递为参数或在函数本身中定义的内容。我不确定这是否有影响,但我可以理解为什么会有影响。

涉及任何查询过程的Getter值,例如数组长度或DOM节点属性,也可能最好缓存到变量中。

但除此之外,我会尝试让工作避免的基本原则指导您的性能努力。预先计算不需要在循环中重新计算的事物,或将查询选择器结果缓存到var而不是反复在DOM中搜寻,这些都是很好的例子。试图过分利用JIT行为可能会变得相当深奥,并且不太可能随着时间的推移或跨越所有JITs而持续。

旧答案:

好的,忘记长篇大论。要点如下:

var i = someArray.length; //length is cached
someArray.reverse(); //include this only if iterating in 0-(length-1) order is important

while(i--){
//run a test statement on someArray[i];
}
  • 长度已缓存并立即转换为索引

  • 在 JavaScript 中向后迭代的好处是避免使用具有两个操作数的逻辑运算符。在这种情况下,我们只是评估一个数字。它是真的还是零和假。

  • 我也觉得它很优美。


1

在每次迭代中从array读取length可能会很慢,但是forEach通常更慢,因为在js中函数调用不是廉价操作。

附注:forEach在FF10上比较慢14%。


我认为这仍然很重要,但我相信即时编译器(JITs)使函数调用更加迅速。 - Erik Reppen
我也是 =) 特别是在那之后,通过将函数内联,我将 CPU 密集型 JS 代码的速度提高了约 5 倍。 - kirilloid
无论我尝试什么,forEach 在 Chrome 中总是更快。 - trusktr
4年前不知道这一点,但现代JIT实际上会在适当的情况下缓存内联函数。在旧的解释器中,函数调用的构建/拆除过程使它们在循环中不受欢迎。现在它们实际上可以进行性能优化。最好避免引用任何未在函数中定义或作为参数传递给它的内容。 - Erik Reppen

0

在 Opera 中对我来说它们大致相同。需要注意的是,您在 for() 中的条件是 array.length。如果您将数组的长度缓存到变量中,然后循环,您应该会看到更好的性能。


0

也许 for() 循环较慢是因为每次迭代都会应用 'array.length' 来获取数组的长度。

尝试:

var nri = array.length;
for( var i = 0; i < nri; i++ ){
   // ...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接