反向循环真的更快吗?

时间:2009-08-27 11:53:11

标签: javascript optimization loops for-loop while-loop

我听过很多次了。向后计数时JavaScript循环真的更快吗?如果是这样,为什么?我已经看到一些测试套件示例显示反向循环更快,但我找不到任何解释为什么!

我假设这是因为循环不再需要在每次检查它是否已完成时评估属性,而只是检查最终的数值。

即。

for (var i = count - 1; i >= 0; i--)
{
  // count is only evaluated once and then the comparison is always on 0.
}

34 个答案:

答案 0 :(得分:874)

i--并不比i++快。实际上,他们都同样快。

升序循环需要花费时间的是评估每个i数组的大小。在这个循环中:

for(var i = array.length; i--;)

当您声明.length时,您只评估i一次,而对于此循环

for(var i = 1; i <= array.length; i++)

每次增加.length时,当您检查i时是否评估i <= array.length

在大多数情况下,你甚至不应该担心这种优化

答案 1 :(得分:192)

This guy在很多浏览器中比较了javascript中的很多循环。他还有一个test suite所以你可以自己运行它们。

在所有情况下(除非我在阅读中错过了一个),最快的循环是:

var i = arr.length; //or 10
while(i--)
{
  //...
}

答案 2 :(得分:126)

我试着用这个答案给出一个全面的了解。

括号中的以下想法是我的信念,直到我刚刚测试了这个问题:

[[对于低级语言,如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);
});

它也不易出错,浏览器有机会优化您的代码。

[备注:不仅是浏览器,而且你也有空间可以轻松优化,只需重新定义forEach功能(浏览器依赖),以便它使用最新的最佳技巧! :) @ A.M.K。在特殊情况下说,值得使用array.poparray.shift。如果你那样做,就把它放在幕后。 极端矫枉过正是向forEach添加选项以选择循环算法。]

此外,对于低级语言,最佳做法是在可能的情况下使用一些智能库函数进行复杂的循环操作。

这些库也可以把东西(多线程)放在你的背后,专业程序员也可以让它们保持最新状态。

我做了更多的审查,结果发现在C / C ++中, 即使对于5e9 =(50,000x100,000)操作,上升和下降之间没有区别如果测试是针对像@alestanis这样的常量进行的。 (JsPerf结果有时不一致,但总的来说也是如此:你不能有很大的不同。)
所以--i恰好是一个“豪华”的东西。它只会让你看起来像一个更好的程序员。 :)

另一方面,为了展开这个5e9的情况,当我走了10秒时,它让我从12秒降到2.5秒,当我走了20秒时,它降到了2.1秒。没有优化,优化使得事情变得无法实现。 :)(展开可以通过上面的方式或使用i++完成,但这并不能在JavaScript中引领前提。)

总而言之:保持i-- / i++++i / i++与求职面谈的差异,坚持{{1}或其他复杂的库函数(如果可用)。 ;)

答案 3 :(得分:33)

i--i++

一样快

以下代码与您的代码一样快,但使用额外的变量:

var up = Things.length;
for (var i = 0; i < up; i++) {
    Things[i]
};

建议不要每次都评估数组的大小。对于大型阵列,可以看到性能下降。

答案 4 :(得分:20)

由于您对此主题感兴趣,请查看Greg Reimer关于JavaScript循环基准测试的博客帖子 What's the Fastest Way to Code a Loop in JavaScript?

  

我为JavaScript中的不同编码循环方法构建了一个循环基准测试套件。已经有一些已经存在,但我没有发现任何承认本机数组和HTML集合之间的区别。

您还可以通过打开https://blogs.oracle.com/greimer/resource/loop-test.html执行循环性能测试(如果JavaScript在浏览器中被阻止,则不起作用,例如NoScript)。

修改

Milan Adamovsky创建的最新基准测试可以在不同浏览器的运行时here中执行。

对于在Mac OS X 10.6上的Firefox 17.0中进行测试,我得到以下循环:

var i, result = 0;
for (i = steps - 1; i; i--) {
  result += i;
}

最快的前提是:

var result = 0;
for (var i = steps - 1; i >= 0; i--) {
  result += i;
}

Benchmark example.

答案 5 :(得分:18)

不是--++,而是比较操作。使用--,您可以使用0比较,而使用++则需要将其与长度进行比较。在处理器上,与零比较通常是可用的,而与有限整数比较则需要减法。

a++ < length

实际编译为

a++
test (a-length)

因此编译时处理器需要更长的时间。

答案 6 :(得分:15)

简短回答

对于普通代码,尤其是JavaScript等高级语言,i++i--没有性能差异。

性能标准是for循环和比较语句中的用法。

适用于所有高级语言,并且大部分独立于JavaScript的使用。解释是生成汇编代码的底线。

详细说明

循环中可能会出现性能差异。背景是,在汇编代码级别,您可以看到compare with 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语句中使用更昂贵的比较语句

只需我5美分

对于高级语言而言,可读性(这有助于提高可维护性)作为轻微的性能改进更为重要。

通常,经典迭代从数组开始到结束更好。

从数组 end到start 的更快迭代会导致可能不需要的反向序列。

Post scriptum

正如评论中所述:--ii--的差异在于在递减之前或之后对i的评估。

最好的解释是尝试一下;-)这是一个Bash例子。

 % i=10; echo "$((--i)) --> $i"
 9 --> 9
 % i=10; echo "$((i--)) --> $i"
 10 --> 9

答案 7 :(得分:14)

我在Sublime Text 2中看到过相同的推荐。

就像已经说过的那样,主要的改进不是在for循环中的每次迭代中评估数组的长度。这是一种众所周知的优化技术,当数组是HTML文档的一部分时(对所有for元素执行li),在JavaScript中特别有效。

例如,

for (var i = 0; i < document.getElementsByTagName('li').length; i++)

慢得多

for (var i = 0, len = document.getElementsByTagName('li').length; i < len; i++)

从我的立场来看,你问题中表单的主要改进是它没有声明一个额外的变量(在我的例子中len

但是如果你问我,重点不在于i++ vs i--优化,而是在每次迭代时不必评估数组的长度(你可以看到一个基准测试在jsperf)。

答案 8 :(得分:13)

由于其他答案似乎都没有回答您的具体问题(其中一半以上显示C示例并讨论低级语言,您的问题是针对JavaScript)我决定编写自己的问题。

所以,你走了:

简单回答: i--通常更快,因为每次运行时都不必将比较运行为0,各种方法的测试结果如下:

测试结果:this jsPerf“证实”,arr.pop()实际上是迄今为止最快的循环。但是,如您在问题中所提到的那样关注--ii--i++++i,这里是jsPerf(它们来自多个jsPerf,请参阅下面的来源)结果总结:

Firefox中的{p> --ii--相同,而Chrome中的i--速度更快。

在Chrome中,基本for循环(for (var i = 0; i < arr.length; i++))比i----i快,而在Firefox中速度较慢。

在Chrome和Firefox中,缓存arr.length的速度显着提高,Chrome提前约170,000次操作/秒。

如果没有显着差异,++i在大多数浏览器中都比i++快,AFAIK,在任何浏览器中都不会相反。

更短的摘要: arr.pop()是迄今为止最快的循环;对于具体提到的循环,i--是最快的循环。

来源: http://jsperf.com/fastest-array-loops-in-javascript/15http://jsperf.com/ipp-vs-ppi-2

我希望这能回答你的问题。

答案 9 :(得分:13)

我不认为有必要说i--比JavaScript中的i++更快。

首先,完全取决于JavaScript引擎的实现。

其次,如果最简单的构造JIT并转换为本机指令,那么i++ vs i--将完全依赖于执行它的CPU。也就是说,在ARM(移动电话)上,由于在单个指令中执行递减和比较为零,因此下降到0会更快。

可能你认为一个人比另一个人更浪漫,因为建议的方式是

for(var i = array.length; i--; )

但建议的方式不是因为一个比另一个快,而只是因为如果你写

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

然后在每次迭代array.length都必须进行评估(更聪明的JavaScript引擎可能会发现循环不会改变数组的长度)。即使它看起来像一个简单的声明,它实际上是一些由JavaScript引擎调用的功能。

另一个原因,为什么可以考虑i--&#34;更快&#34;是因为JavaScript引擎只需要分配一个内部变量来控制循环(变量为var i)。如果你将array.length或其他变量进行比较,则必须有多个内部变量来控制循环,而内部变量的数量是JavaScript引擎的有限资产。循环中使用的变量越少,JIT进行优化的机会就越大。这就是i--被认为更快的原因......

答案 10 :(得分:12)

取决于您在访问该阵列时数组在内存中的位置以及内存页的命中率。

在某些情况下,由于命中率的增加,按列顺序访问数组成员的速度比行顺序快。

答案 11 :(得分:10)

我最后一次打扰它是在写6502汇编时(8位,是啊!)。最大的收获是大多数算术运算(尤其是递减)更新了一组标志,其中一个是Z,“达到零”指示。

所以,在循环结束时你只做了两个指令:DEC(递减)和JNZ(如果不是零则跳转),不需要比较!

答案 12 :(得分:7)

简而言之:在JavaScript中执行此操作绝对没有区别。

首先,您可以自己测试一下:

您不仅可以在任何JavaScript库中测试和运行任何脚本,而且还可以访问大量以前编写的脚本,以及查看不同平台上不同浏览器的执行时间差异的能力。 / p>

因此,就您所见,任何环境中的性能都没有区别。

如果您想提高脚本的性能,可以尝试执行以下操作:

  1. 拥有var a = array.length;语句,以便每次在循环中都不会计算其值
  2. 循环展开http://en.wikipedia.org/wiki/Loop_unwinding
  3. 但你必须明白,你可以获得的改善是如此微不足道,大多数你甚至不应该关心它。

    我自己认为为什么出现这种误解(Dec vs Inc)

    很久很久以前,有一个共同的机器指令DSZ(减少并略过零)。使用汇编语言编程的人使用此指令实现循环以保存寄存器。现在这个古老的事实已经过时了,我很确定使用这种伪改进你不会在任何语言中获得任何性能提升。

    我认为这种知识在我们这个时代传播的唯一方式就是当你阅读另一个人的代码时。看到这样的结构,并问为什么它实现了,在这里答案:“它提高了性能,因为它比较为零”。你对同事的更高知识感到困惑,并认为使用它更聪明:-)

答案 13 :(得分:6)

for(var i = array.length; i--; )速度不是很快。但是当您将array.length替换为super_puper_function()时,可能显着更快(因为它在每次迭代中都被调用)。这就是区别。

如果您打算在2014年进行更改,则无需考虑优化。如果您要使用“搜索和替换”进行更改,则无需考虑优化。如果您没有时间,则无需考虑优化。但现在,你有时间考虑它。

P.S。:i--并不比i++快。

答案 14 :(得分:6)

这不取决于--++符号,但这取决于您在循环中应用的条件。

例如:如果变量的静态值比每次循环检查条件的静态值更快,例如数组的长度或其他条件,则循环更快。

但是不要担心这种优化,因为这次它的效果是以纳秒为单位测量的。

答案 15 :(得分:6)

可以通过JavaScript(以及所有语言)最终将其转换为在CPU上运行的操作码来解释。 CPU总是有一条指令用于与零进行比较,这很快。

顺便说一句,如果您可以保证count始终为>= 0,则可以简化为:

for (var i = count; i--;)
{
  // whatever
}

答案 16 :(得分:5)

我做了comparison on jsbench

正如alestani指出的那样,在升序循环中花费时间的一件事是,为每次迭代评估数组的大小。在这个循环中:

for ( var i = 1; i <= array.length; i++ )

每次增加.length时评估i。在这一个:

for ( var i = 1, l = array.length; i <= l; i++ )

当您声明.length时,您只评估i一次。在这一个:

for ( var i = array.length; i--; )

比较是隐式的,它在递减i之前发生,并且代码非常易读。然而,什么可以产生一个很大的差异,是你放在循环中。

循环调用函数(在别处定义):

for (i = values.length; i-- ;) {
  add( values[i] );
}

使用内联代码循环:

var sum = 0;
for ( i = values.length; i-- ;) {
  sum += values[i];
}

如果您可以内联代码,而不是在不牺牲易读性的情况下调用函数,那么可以使循环速度提高一个数量级!


注意:由于浏览器是becoming good at inlining个简单函数,它实际上取决于代码的复杂程度。因此,优化之前的配置文件,因为

  1. 瓶颈可能在其他地方(ajax,reflow,......)
  2. 您可以选择更好的算法
  3. 您可以选择更好的数据结构
  4. 但请记住:

      

    编写代码供人们阅读,并且只是偶然让机器执行。

答案 17 :(得分:4)

有时对我们编写代码的方式进行一些非常小的更改可能会对代码实际运行的速度产生很大影响。较小的代码更改可以对执行时间产生重大影响的一个领域是我们有一个处理数组的for循环。如果数组是网页上的元素(例如单选按钮),则更改效果最好,但即使数组是Javascript代码的内部数据,仍然值得应用此更改。

编码for循环以处理数组的传统方法如下:

for (var i = 0; i < myArray.length; i++) {...

这个问题是使用myArray.length评估数组的长度需要时间,而我们编写循环的方式意味着每次循环都必须执行此评估。如果数组包含1000个元素,则数组的长度将被评估1001次。如果我们查看单选按钮并且有myForm.myButtons.length那么评估将花费更长的时间,因为必须首先定位指定表单中的适当的按钮组,然后才能在循环周围评估长度。

显然,在我们处理数组时,我们不希望数组的长度发生变化,因此所有这些长度的重新计算只会不必要地增加处理时间。 (当然如果你在循环中添加或删除数组条目的代码,那么数组大小可以在迭代之间改变,所以我们不能改变测试它的代码)

对于修复大小的循环,我们可以做的是在循环开始时计算一次长度并将其保存在变量中。然后我们可以测试变量以决定何时终止循环。这比每次评估数组长度要快得多,特别是当数组包含的条目不仅仅是几个条目或者是网页的一部分时。

执行此操作的代码是:

for (var i = 0, var j = myArray.length; i < j; i++) {...

所以现在我们只评估一次数组的大小,并针对每次循环时保存该值的变量测试我们的循环计数器。这个额外的变量可以比评估数组的大小快得多地访问,因此我们的代码运行速度比以前快得多。我们的脚本中只有一个额外的变量。

只要数组中的所有条目都得到处理,我们处理数组的顺序通常无关紧要。在这种情况下,我们可以通过取消刚刚添加的额外变量并以相反的顺序处理数组来使代码稍快一些。

以最有效的方式处理我们的数组的最终代码是:

for (var i = myArray.length-1; i > -1; i--) {...

此代码仍然只在开始时评估数组的大小,但不是将循环计数器与变量进行比较,而是将它与常量进行比较。由于常量比变量更有效,因为我们的赋值语句比之前少了一个,我们的第三个版本的代码现在比第二个版本稍微高效一点,效率远高于第一个版本。

答案 18 :(得分:4)

你现在这样做的方式并不快(除了它是一个无限循环,我想你的意思是i--

如果你想让它更快,请执行:

for (i = 10; i--;) {
    //super fast loop
}

当然你不会在如此小的循环中注意到它。它更快的原因是因为你在检查它是“真”时递减i(它在达到0时评估为“假”)

答案 19 :(得分:3)

使用来表示--i更快(在C ++中),因为只有一个结果,即减少的值。 i--需要将递减的值存储回i并且还保留原始值作为结果(j = i--;)。在大多数编译器中,这会占用两个寄存器而不是一个寄存器,这可能导致另一个变量必须写入存储器而不是保留为寄存器变量。

我同意那些说这些日子没有任何区别的人。

答案 20 :(得分:3)

在许多情况下,这与处​​理器可以比其他比较更快地比较零的事实无关。

这是因为only a few Javascript engines(JIT列表中的那些)实际上生成了机器语言代码。

大多数Javascript引擎构建源代码的内部表示,然后他们解释(为了了解它是什么样的,看看this page on Firefox's SpiderMonkey的底部附近)。通常,如果一段代码几乎完全相同,但会导致更简单的内部表示,它将运行得更快。

请记住,通过简单的任务,例如从变量中添加/减去一个变量,或者将变量与某个变量进行比较,解释器从一个内部“指令”移动到下一个“指令”的开销非常高,因此“指令越少” “JS引擎内部使用的越多越好。

答案 21 :(得分:3)

好吧,我不知道JavaScript,它应该只是重新评估数组长度的问题,也许与关联数组有关(如果你只是递减,那么新条目不太可能需要)要分配 - 如果数组是密集的,那就是有人可以优化它。)

在低级程序集中,有一个循环指令,称为DJNZ(如果非零,则递减和跳转)。所以递减和跳转都在一条指令中,使得它可能比INC和JL / JB稍微快一点(增量,如果低于/ jump则跳跃,如果低于/)。此外,与零进行比较比与另一个数字进行比较更简单。但所有这些都是微不足道的,也取决于目标体系结构(例如可以在智能手机上的Arm上产生差异)。

我不希望这种低级差异对解释语言产生如此大的影响,我在回答中看不到DJNZ,所以我想我会分享一个有趣的想法。

答案 22 :(得分:3)

i - 或i ++消耗的时间不多。如果深入了解CPU体系结构,++--更快,因为--操作将执行2的补码,但它发生在硬件内部,因此这将使它成为++--之间的快速且没有重大差异,这些操作也被认为是CPU消耗的最少时间。

for循环的运行方式如下:

  • 在开始时初始化变量一次。
  • 检查循环的第二个操作数中的约束,<><=等。
  • 然后应用循环。
  • 再次增加循环并循环再次抛出这些进程。

所以,

for (var i = Things.length - 1; i >= 0; i--) {
    Things[i]
}; 

将在开始时仅计算一次数组长度,这不是很多时间,而是

for(var i = array.length; i--; ) 

将计算每个循环的长度,因此会耗费大量时间。

答案 23 :(得分:3)

首先,i++i--在任何编程语言(包括JavaScript)上完全相同。

以下代码需要不同的时间。

快速:

for (var i = 0, len = Things.length - 1; i <= len; i++) { Things[i] };

慢速:

for (var i = 0; i <= Things.length - 1; i++) { Things[i] };

因此,以下代码也需要不同的时间。

快速:

for (var i = Things.length - 1; i >= 0; i--) { Things[i] };

慢速:

for (var i = 0; i <= Things.length - 1; i++) { Things[i] };

P.S。由于编译器的优化, Slow 对于少数几种语言(JavaScript引擎)来说很慢。最好的方法是使用'&lt;'而是'&lt; ='(或'=')和' - 我'而不是'我 - '

答案 24 :(得分:3)

用非常简单的话说

“我 - 和我+ ++。实际上,它们都需要同一时间”。

但是在这种情况下,当你有增量操作时..处理器在每次变量增加1时计算.length并且在减少的情况下...特别是在这种情况下,它将仅评估.length一次直到我们得到0。

答案 25 :(得分:3)

++--无关,因为JavaScript是一种解释型语言,而不是编译语言。每条指令都转换为多种机器语言,您不应该关心血腥细节。

正在谈论使用--(或++)来有效使用汇编指令的人是错误的。这些指令适用于整数运算,有no integers in JavaScript, just numbers

您应该编写可读代码。

答案 26 :(得分:1)

帮助其他人避免头痛---再次投票!!!

此页面上最受欢迎的答案不适用于Firefox 14,也不会通过jsLinter。 “while”循环需要一个比较运算符,而不是赋值。它确实适用于铬,野生动物园,甚至ie。但是在firefox中死了。

这已经破产了!

var i = arr.length; //or 10
while(i--)
{
  //...
}

这会奏效! (适用于firefox,通过jsLinter)

var i = arr.length; //or 10
while(i>-1)
{
  //...
  i = i - 1;
}

答案 27 :(得分:1)

喜欢它,很多标记但没有答案:D

简单地对零进行比较始终是最快的比较

所以(a == 0)实际上在返回True时比(a == 5)

更快

它很小而且微不足道,并且在一个集合中有1亿行可以测量。

即在一个循环上你可能会说我在哪里&lt; = array.length并且正在递增i

在一个向下循环中你可能会说我在哪里&gt; = 0并且正在递减i。

比较更快。不是循环的“方向”。

答案 28 :(得分:1)

编译器是否会缓存.length,因此如果您要比较0或.length则没有区别?我想这对你正在处理的编译器或解释器非常具体。

我想说如果您使用的是优化良好的编译器或解释器,那么您不必担心这一点,这是语言开发人员所担心的。

答案 29 :(得分:1)

回答这类问题的最佳方法是实际尝试。设置一个计算一百万次迭代或其他任何内容的循环,并以两种方式完成。时间都循环,并比较结果。

答案可能取决于您使用的浏览器。有些人会有不同的结果。

答案 30 :(得分:1)

这只是猜测,但也许是因为处理器更容易比较0(i&gt; = 0)而不是其他值(i&lt; Things.length)。

答案 31 :(得分:0)

我想为这个线程贡献一个跨浏览器的JavaScript中最快的循环!与反向while循环相比,此循环产生了超过500%的改进。

My Blog: Fastest Loop in JavaScript

答案 32 :(得分:0)

使用前缀增量运算符稍快一些。使用后缀,编译器必须保留以前的值作为表达式的结果。

for (var i = 0; i < n; ++i) {
  do_stuff();
}

智能解释器或编译器会看到没有使用i ++的结果而不存储表达式的结果,但并不是所有的js引擎都能存储。

答案 33 :(得分:-2)

减量模式的最快方法:

var iteration = 10000000;
    while(iteration > 0){
        iteration--;
    }

更快:

var iteration = 10000000;
    while(iteration--);

Javascript micro optimisation test