array.forEach 比本机迭代运行得更快?如何?

Posted

技术标签:

【中文标题】array.forEach 比本机迭代运行得更快?如何?【英文标题】:array.forEach running faster than native iteration? How? 【发布时间】:2012-04-16 10:03:24 【问题描述】:

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

我的理解是测试用例 2 应该比测试用例 1 运行得更慢——我想看看慢多少。想象一下,当我看到它运行得更快时,我会感到惊讶!

这里发生了什么?幕后优化?还是 .forEach 更清洁、更快?

在 Windows Server 2008 R2 / 7 64 位上的 Chrome 18.0.1025.142 32 位测试

【问题讨论】:

forEach 可能有一些原生优化,或者其他什么。 更重要的是,您甚至没有从测试用例 1 中的数组中获取值,您正在记录 i 而不是 array[i] 1) 您不应该在每个测试中都创建数组,您可以定义可在所有测试中访问的变量 2) 您正在检查 .length 每个循环 如果您运行版本 7 jsperf.com/testing-foreach-vs-for-loop/7,结果很有趣,并且这些 cmets 中提到的问题已得到纠正。 【参考方案1】:

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

缓存数组长度 向后迭代 使用 ++counter 代替 counter++

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

有关一些示例,请参阅 this jsperf。

编辑: postfix vs prefix perf test 和 iterating backwards 的链接。 我找不到使用 +=1 而不是 ++ 的参考资料,因此我已将其从列表中删除。

【讨论】:

听说对于C来说++i比i++快,不知道是不是也适用于javascript 向后迭代并使用 +=1 而不是 ++ 实际上在 JS 中没有任何改变。 @kirilloid += 和 ++ 是两个完全不同的运算符,它们可以在不同的解析器实现中以不同的速度运行,而递减与递增是众所周知的微优化。我将编辑以添加参考。 不幸的是,js 距离例如C。在Cjs 中,向后迭代可能会中断一个(或可能是2 个)CPU 操作,但JS 循环中的一个操作太小。 JS 解析器也可以优化。不用说,死代码消除优化器可能会在您的示例中丢弃一些代码。并且完全搞砸了结果。 注意:链接到向后循环的帖子可能已经过时,因为浏览器已经发生了很大变化。我测试了许多声称在速度上有差异但在更新的浏览器上没有发现的东西。 (我并不是说是这种情况,只是要注意浏览器会不断发展)【参考方案2】:

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

【讨论】:

【参考方案3】:

在每次迭代中从array 读取length 可能会很慢,但 forEach 通常会更慢,因为函数调用在 js 中并不便宜。

PS:forEach 在 FF10 上慢了 14%。

【讨论】:

我认为这仍然很重要,但我相信 JIT 已经使函数调用变得更加快捷。 我也是 =) 尤其是在那之后,我仅使用内联函数将 CPU 密集型 JS 代码加速了约 5 倍。 无论我尝试什么,forEach 在 Chrome 中总是更快。 4 年前不知道这一点,但这是因为现代 JIT 实际上在适当的情况下缓存内联函数。在旧的解释器中,函数调用的构建/拆卸过程使它们在循环中不受欢迎。现在他们实际上可以进行性能优化。最好避免引用函数中未定义或作为参数传递给它的任何内容。【参考方案4】:

也许 for() 比较慢,因为循环将 'array.length' 应用于每次迭代,以获取数组的长度。

试试:

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

【讨论】:

【参考方案5】:

更新:

这些答案中的许多旧技巧非常适合在旧浏览器中解释 JS。

在任何现代 JS 实现中,包括所有现代浏览器、Node 和最新的移动 web 视图,内联函数实际上可以由 JIT(JS 编译器)缓存,这使得 forEach 通常成为数组迭代更快的选项。过去恰恰相反,只是重复调用一个函数需要一个构建/拆卸过程,这可能会严重降低非平凡循环的性能。

为了获得最佳性能,如果您不需要,我会避免引用任何未作为参数传递或在函数本身内部定义的内容。我不是 100% 确定这很重要,但我知道为什么会这样。

涉及任何类型的查找过程(如数组长度或 DOM 节点属性)的 Getter 值也可能最好还是缓存到变量中。

但除此之外,我会尽量让避免工作的基本原则指导你的表现。预先计算不需要在循环中重新计算的东西,或者将查询选择器结果缓存到 var 而不是在 DOM 中反复翻找就是很好的例子。过于努力地利用 JIT 行为可能会变得相当晦涩,并且不太可能随着时间的推移或在所有 JIT 中保持不变。

旧答案:

好的,忘记文字墙。要点:

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];

长度被缓存并立即进入索引

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

我也觉得它很优雅。

【讨论】:

以上是关于array.forEach 比本机迭代运行得更快?如何?的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV,捕获的视频比原始相机视频运行得更快!

为啥我的线性搜索比我在 Python3 中的二分搜索运行得更快?

为啥我的 O(NLogN) 算法查找字谜比我的 O(N) 算法运行得更快?

SQL 导出到平面文件比作业中 SSIS 中的相同导出运行得更快

当我对一个程序进行采样并且它实际上比不进行分析时运行得更快时,为啥会这样?

为啥使用第一个阅读器 read() 运行第二个阅读器比在自己的阅读器上运行它运行得更快?