我听过这个问题很多次。JavaScript 循环倒序计数真的更快吗?如果是这样,为什么呢?我看到一些测试套件示例显示反向循环更快,但我找不到任何解释为什么!
我猜这是因为循环不再需要每次评估一个属性来检查它是否完成,而只需针对最终数字值进行检查。
即:
for (var i = count - 1; i >= 0; i--)
{
// count is only evaluated once and then the comparison is always on 0.
}
我听过这个问题很多次。JavaScript 循环倒序计数真的更快吗?如果是这样,为什么呢?我看到一些测试套件示例显示反向循环更快,但我找不到任何解释为什么!
我猜这是因为循环不再需要每次评估一个属性来检查它是否完成,而只需针对最终数字值进行检查。
即:
for (var i = count - 1; i >= 0; i--)
{
// count is only evaluated once and then the comparison is always on 0.
}
并不是i--
比i++
更快。 实际上,它们都一样快。
在升序循环中需要时间的是为每个i
计算数组大小。 在这个循环中:
for(var i = array.length; i--;)
在声明 i
时,您只评估了 .length
一次,而对于此循环:
您仅评估了 .length
一次,这发生在声明 i
时,而不是在每次循环迭代中重新评估。
for(var i = 1; i <= array.length; i++)
每次增加i
并检查是否i <= array.length
时,你都要评估.length
。
在大多数情况下,你甚至不需要担心这种优化。
.length
多次是否比声明和定义一个新变量更快。在大多数情况下,这样的优化是不必要的(并且错误的),除非你的 length
很耗时。 - alestanis0
比 array.length
更快地进行评估。好吧,理论上是这样的。 - Lightness Races in Orbitfor loop
的声明中是否有.length
并不重要。 - Haris Krajina--i
,则在循环中必须使用 var current = arr[i-1];
,否则会导致偏移一个位置。 - DaveAlger我试图通过这个答案给出一个广泛的概述。
以下括号中的想法曾经是我的信仰,直到最近我测试了这个问题:
[[对于低级语言(如C /C++),代码被编译成处理器有一个特殊的条件跳转命令当一个变量为零(或非零)。
此外,如果您非常关心优化,可以使用 ++i
而不是 i++
,因为 ++i
是一个单一的处理器命令,而 i++
意味着 j=i+1, i=j
。]]
可以通过展开循环来实现真正快速的循环:
for(i=800000;i>0;--i)
do_it(i);
它可能比其他方法慢得多
for(i=800000;i>0;i-=8)
{
do_it(i); do_it(i-1); do_it(i-2); ... do_it(i-7);
}
但造成这种情况的原因可能会相当复杂(仅举几个例子,游戏中存在处理器命令预处理和缓存处理等问题)。
就像你所问的那样,在高级语言(比如JavaScript)方面,如果你依赖库和内置函数进行循环,你可以优化代码。让它们决定最佳实践。
因此,在 JavaScript 中,我建议使用类似以下的方法:
array.forEach(function(i) {
do_it(i);
});
此外,使用array.forEach
等复杂库函数是低级语言的最佳实践。这些库可以将操作(多线程)放在背后,并且专业程序员会使它们保持最新状态。同时,在C/C++中,即使进行50亿(50,000 x 100,000)次操作,如果针对常量进行测试,则前缀和后缀没有区别。而对于循环展开,根据具体情况可能会有所帮助。总之,保持i--
/i++
和++i
/i++
的差异集中在面试中,如果有可用的array.forEach
或其他复杂库函数,请使用它们。
array.each(...)
可能会更好。我不认为他们会尝试使用解循环普通的 for 循环进行实验。 - Barney Szabolcs+1
,并获得了一个“优秀回答”徽章。真的是个非常棒的回答。 :) - Rohit Jaini--
和i++
的速度是一样快的。
下面的代码与您的代码一样快,但使用了额外的变量:
var up = Things.length;
for (var i = 0; i < up; i++) {
Things[i]
};
建议不要每次计算数组大小,对于大型数组会导致性能下降。
for (var i=0, up=Things.length; i<up; i++) {}
- thdoanup
与Things.length
不是通过引用(如对象或数组)传递的,而是直接按值传递的。换句话说,它被插入到循环控制中,就像数字10或100000一样。你只是在循环外面重新命名了它,所以没有任何区别。
因此,答案本身是完全有效的。当看到i < up时,循环控制看到的是i < 10而不是i <(对数字10的引用)。实际上,up
内部存储了一个原始值,不要忘记这一点。 - Eve既然您对此感兴趣,可以看一下Greg Reimer关于JavaScript循环基准测试的博客文章:JavaScript中最快的编码循环方式是什么?:
我为不同的JavaScript循环编码方式构建了循环基准测试套件。已经有一些这样的测试了,但我没有找到任何一个承认本机数组和HTML集合之间的差异。
您还可以通过打开https://blogs.oracle.com/greimer/resource/loop-test.html
(如果浏览器被例如NoScript阻止JavaScript,则无法使用)在循环上进行性能测试。
编辑:
米兰·亚当诺夫斯基(Milan Adamovsky)创建的一项较新的基准测试可以在运行时进行,点击此处选择不同的浏览器进行测试。
问题不在于--
或++
,而在于比较操作。使用--
可以与0进行比较,而使用++
需要将其与长度进行比较。在处理器上,通常可以与零进行比较,而与有限整数进行比较则需要减法。
a++ < length
实际上编译为:
a++
test (a-length)
编译时在处理器上需要更长的时间。
我认为在JavaScript中说i--
比i++
快是没有意义的。
首先,这完全取决于JavaScript引擎的实现。
其次,假设最简单的结构被即时编译并转换成本机指令,则i++
与i--
将完全取决于执行它的CPU。也就是说,在ARM(手机)上,向0下降更快,因为递减和与零比较是在单个指令中执行的。
也许你认为其中一个比另一个更快,因为建议使用的方式是
for(var i = array.length; i--; )
但建议的方式并不是因为其中一个更快,而只是因为如果您编写
for(var i = 0; i < array.length; i++)
在每次迭代时,array.length
都必须被计算 (更聪明的JavaScript引擎可能能够推断出循环不会改变数组的长度)。虽然它看起来像一个简单的语句,但实际上是由JavaScript引擎在幕后调用的一些函数。
另一个原因,为什么 i--
被认为是“更快”的是因为JavaScript引擎只需要分配一个内部变量来控制循环(即 var i
变量)。如果与 array.length
或其他变量进行比较,则需要使用多个内部变量来控制循环,而内部变量的数量是JavaScript引擎的有限财产。循环中使用的变量越少,JIT就越有机会进行优化。这就是为什么 i--
可以被认为是更快的原因...
array.length
,需要谨慎措辞。引用它时,并不会进行计算(它只是一个属性,在创建或更改数组索引时设置)。如果有额外的开销,那是因为JS引擎没有针对该名称优化查找链。 - kojirogetLength(){return m_length;}
,因为其中涉及到一些维护工作。但是如果你试着往回思考:编写数组实现,其中长度实际上需要计算,那将是相当巧妙的 :) - Pavel Plength
属性。每当添加或更改一个作为数组索引的属性时,length
必须立即更新。 - kojirodec/jnz
相当于 inc eax / cmp eax, edx / jne
。 - Peter Cordes对于正常的代码,特别是在高级语言如JavaScript中,i++
和i--
没有性能差异。
性能的标准是在for
循环中和比较语句中的使用。
这个规则适用于所有高级语言,并且大多数独立于JavaScript的使用。解释在于最终生成的汇编代码。
循环中可能会出现性能差异。背景是,在汇编代码层面上,您可以看到与0比较
只是一个语句,不需要额外的寄存器。
每次循环都会发出此比较,并可能导致可衡量的性能提升。
for(var i = array.length; i--; )
将被评估为类似于伪代码的形式:
i=array.length
:LOOP_START
decrement i
if [ i = 0 ] goto :LOOP_END
... BODY_CODE
:LOOP_END
请注意,0是一个字面常量,也就是说它是一个不变的值。
for(var i = 0 ; i < array.length; i++ )
将被评估为类似于这样的伪代码(假定正常解释器优化):
end=array.length
i=0
:LOOP_START
if [ i < end ] goto :LOOP_END
increment i
... BODY_CODE
:LOOP_END
请注意,end是一个需要CPU寄存器的变量。这可能会在代码中引发额外的寄存器交换,并需要在if
语句中使用更昂贵的比较语句。
对于高级语言,可读性(有助于可维护性)比轻微的性能改进更重要。
通常从数组开始到结束的经典迭代更好。
更快的从数组末尾到开头的迭代会导致可能不需要的反向序列。
正如评论所问: --i
和i--
的区别在于对i
进行递减之前还是之后进行评估。
最好的解释是试一下;-) 这里是一个Bash示例。
% i=10; echo "$((--i)) --> $i"
9 --> 9
% i=10; echo "$((i--)) --> $i"
10 --> 9
--i
和i--
之间的区别呢? - Afshin Mehrabani我在Sublime Text 2中看到了同样的建议。
正如之前所说,主要的改进并不是在for循环的每次迭代中评估数组的长度。这是一种众所周知的优化技术,在JavaScript中特别有效,当数组是HTML文档的一部分时(为所有li元素执行for循环)。
例如,
for (var i = 0; i < document.getElementsByTagName('li').length; i++)
比
for (var i = 0, len = document.getElementsByTagName('li').length; i < len; i++)
慢得多。
从我的角度来看,在你的问题中,主要的改进在于它没有声明额外的变量(例如我的示例中的len)
但是如果问我,整个重点不在于i++与i--的优化,而在于不必在每次迭代中评估数组的长度(可以在jsperf上看到基准测试结果)。
由于其他答案似乎没有回答你的具体问题(其中超过一半显示C示例并讨论较低级别的语言,你的问题是关于JavaScript的),所以我决定自己写一个。
因此,在这里给你:
简单回答:i--
通常更快,因为它不必每次运行时都运行与0的比较,各种方法的测试结果如下:
测试结果:如这个 jsPerf“证明”的那样,arr.pop()
实际上是远远最快的循环。但是,专注于--i
、i--
、i++
和 ++i
,如你在问题中提到的,以下是jsPerf (它们来自多个jsPerf,请参见下面的来源)的结果摘要:
在Firefox中,--i
和i--
相同,而在Chrome中i--
更快。
在Chrome中,基本的for循环(for (var i = 0; i < arr.length; i++)
)比i--
和--i
更快,而在Firefox中则更慢。
在Chrome和Firefox中,缓存的arr.length
明显更快,其中Chrome领先约170,000 ops/sec。
没有显着差异,++i
在大多数浏览器中比i++
更快,据我所知,在任何浏览器中都不会反过来。
简要总结:arr.pop()
是远远最快的循环;对于特别提到的循环,i--
是最快的循环。
来源:http://jsperf.com/fastest-array-loops-in-javascript/15,http://jsperf.com/ipp-vs-ppi-2
希望这回答了你的问题。
pop
测试之所以看起来很快,是因为在大多数循环中它将数组大小减少到了0。但是为了确保公平,我设计了这个 jsperf 来以相同的方式创建每个测试的数组 - 这似乎显示 .shift()
在我的几个浏览器中是胜出者 - 这不是我预期的结果 :) http://jsperf.com/compare-different-types-of-looping - Pebbl++i
的人,所以我点了个赞 :D - jaggedsoft
for
循环更快,因为上界(嘿嘿,下界)循环控制变量不需要被定义或从对象中获取;它是一个常数零。 - Josh Stodola