反向循环真的更快吗?

djd*_*d87 259 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.
}
Run Code Online (Sandbox Code Playgroud)

ale*_*nis 889

它不是那么i--i++.实际上,他们都同样快.

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

for(var i = array.length; i--;)
Run Code Online (Sandbox Code Playgroud)

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

for(var i = 1; i <= array.length; i++)
Run Code Online (Sandbox Code Playgroud)

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

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

  • @ragatskynet:不,除非你正在设立一个基准并希望说明一点. (39认同)
  • @ragatskynet这取决于:评估`.length`一定次数或声明和定义一个新变量会更快吗?在大多数情况下,这是过早(和错误)优化,除非你的`length`评估非常昂贵. (29认同)
  • 是否值得为array.length引入一个变量并在for循环的头部使用它? (23认同)
  • 值得一提的是,我们正在谈论解释性语言,如Ruby,Python.编译语言(例如Java)在编译器级别上进行了优化,这将"平滑"这些差异,使得`.length`在`for循环'的声明中无关紧要. (21认同)
  • @Drddel博士:这不是比较 - 这是评估.```比`array.length`更快评估.好吧,据说. (10认同)
  • 我总是使用这个:`for(var i = 0,c = array.length; i <c; i ++)`.在任何语言中,它都是一个简单明了的优化,所以它不会受到伤害. (8认同)
  • @Drddel博士没有,我不是说一次评估比另一次更快.我所说的是能够将`i`与`.length`进行比较,你必须*得到*数组的长度,这就需要花费更多时间. (4认同)
  • @Drddel博士:不,因为评估变量需要查找某种类型,而评估"0"则不会因为你已经拥有它的值.这实际上正是我们所说的,因为`array.length`是一个变量. (4认同)
  • 根据这一基准测试,IE的性能提升速度提高了30%.我想对于大型数据集,如果您的观众使用IE,这是值得的.http://www.erichynds.com/javascript/javascript-loop-performance-caching-the-length-property-of-an-array/ (4认同)
  • @Drddel博士:不一定.`array.length`需要两个名称查找.一个用于`array`,然后另一个用于`length`,而变量只需要一个.当然,差异可能微乎其微. (3认同)
  • 我认为这可能会产生误导......我仍在评估并在每个周期与0进行比较.你是说比较i到0比长度更有效吗?这对我来说听起来很可疑. (2认同)
  • 值得指出的是,虽然这里的向后循环在所有版本的IE中比前向版本快得多.由于旧IE是迄今为止最慢的引擎,如果您对后向迭代感到满意,请使用它. (2认同)
  • 还值得一提的是,如果您打算在循环期间删除一个或多个元素,则通过动态数组向后迭代可能很有用。 (2认同)
  • @ H.-DirkSchmitt:这取决于语言和具体评估.例如,在C中,使用`for(i = 0; i <strlen(s); i ++)`迭代列表将在运行时间上产生重大差异,因为`strlen(s)`无法优化并以字符串的长度在线性时间内运行. (2认同)

Tom*_*ter 197

这个人在很多浏览器中比较了javascript中的很多循环.他还有一个测试套件,所以你可以自己运行它们.

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

var i = arr.length; //or 10
while(i--)
{
  //...
}
Run Code Online (Sandbox Code Playgroud)

  • 有意思,但我想知道预先减少是否会更快.因为它不必存储i的中间值. (11认同)
  • ......要明确var i arr.length + 1; 当我) { } (5认同)
  • @tvanfosson如果你预先减少`--i`然后你必须使用`var current = arr [i-1];`在循环内部或它将被一个... (3认同)
  • 我听说i--可能比--i快,因为在第二种情况下,处理器需要递减然后测试新值(指令之间存在数据依赖性),而在第一种情况下处理器可以测试现有值并在一段时间后减少该值.我不确定这是适用于JavaScript还是仅适用于非常低级别的C代码. (2认同)

X''*_*X'' 127

我试着用这个答案给出一个广泛的图景.

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

[[对于像C/C++这样的低级语言,编译代码使得当变量为零(或非零)时处理器具有特殊的条件跳转命令. 此外,如果你关心这么多的优化,你可以去代替,因为是一个单处理器命令,而不是意味着.]]
++ii++++ii++j=i+1, i=j

真正的快速循环可以通过展开它们来完成:

for(i=800000;i>0;--i)
    do_it(i);
Run Code Online (Sandbox Code Playgroud)

它可能比慢

for(i=800000;i>0;i-=8)
{
    do_it(i); do_it(i-1); do_it(i-2); ... do_it(i-7);
}
Run Code Online (Sandbox Code Playgroud)

但其原因可能非常复杂(仅举几例,游戏中存在处理器命令预处理和缓存处理的问题).

高级语言而言,如您所问的JavaScript,如果您依赖库,内置函数进行循环,则可以优化.让他们决定如何做得最好.

因此,在JavaScript中,我建议使用类似的东西

array.forEach(function(i) {
    do_it(i);
});
Run Code Online (Sandbox Code Playgroud)

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

[备注:不仅是浏览器,而且你也有空间可以轻松优化,只需重新定义forEach功能(浏览器依赖),以便它使用最新的最佳技巧!:) @AMK说在特殊情况下值得使用array.pop或者array.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++差异到求职面试,坚持array.forEach或其他复杂的图书馆功能可用.;)

  • 关键词是"可以".您的展开循环也可能比原始循环慢.优化时,请始终进行衡量,以便准确了解更改的影响. (18认同)

sai*_*iuc 33

i-- 和...一样快 i++

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

var up = Things.length;
for (var i = 0; i < up; i++) {
    Things[i]
};
Run Code Online (Sandbox Code Playgroud)

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

  • 你在这里显然是错的.现在循环控制需要额外的内部变量(i和up),并且根据JavaScript引擎,这可能会限制优化代码的可能性.JIT将简单循环转换为直接机器操作码,但如果循环有太多变量,那么JIT将无法优化.通常,它受JIT使用的体系结构或cpu寄存器的限制.初始化一次,最简单的解决方案,这就是为什么它被推荐到处. (5认同)
  • 最好将“up”放在循环内:“for (var i=0, up=Things.length; i&lt;up; i++) {}” (2认同)

dre*_*ash 21

既然,您对这个主题感兴趣,请看一下Greg Reimer关于JavaScript循环基准的博客文章,在JavaScript中编写循环的最快方法是什么?:

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

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

编辑:

Milan Adamovsky创建的最新基准测试可以在此处针对不同的浏览器在运行时执行.

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

var i, result = 0;
for (i = steps - 1; i; i--) {
  result += i;
}
Run Code Online (Sandbox Code Playgroud)

最快的是:

var result = 0;
for (var i = steps - 1; i >= 0; i--) {
  result += i;
}
Run Code Online (Sandbox Code Playgroud)

基准的例子.

  • 那篇文章现在已经4岁了.考虑到JavaScript引擎更新的速度(ha!),大多数建议可能已经过时 - 文章甚至没有提及Chrome及其闪亮的V8 JavaScript引擎. (5认同)

Dr *_*ams 19

它不是--或者++,它是比较操作.有了--您可以使用与0的比较,同时用++你需要将它与长度进行比较.在处理器上,与零比较通常是可用的,而与有限整数相比则需要减法.

a++ < length

实际编译为

a++
test (a-length)
Run Code Online (Sandbox Code Playgroud)

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


H.-*_*itt 15

简短的回答

对于普通代码,特别是像JavaScript这样的高级语言,在i++和中没有性能差异i--.

性能标准是在for循环和比较语句中的使用.

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

详细解释

循环中可能出现性能差异.背景是在汇编程序代码级别上,您可以看到a compare with 0只是一个不需要额外寄存器的语句.

这种比较是在循环的每次通过时发出的,并且可以导致可测量的性能改进.

for(var i = array.length; i--; )
Run Code Online (Sandbox Code Playgroud)

将被评估为这样的伪代码:

 i=array.length
 :LOOP_START
 decrement i
 if [ i = 0 ] goto :LOOP_END
 ... BODY_CODE
 :LOOP_END
Run Code Online (Sandbox Code Playgroud)

请注意,0是文字,或者换句话说,是一个常量值.

for(var i = 0 ; i < array.length; i++ )
Run Code Online (Sandbox Code Playgroud)

将被评估为这样的伪代码(假设正常的解释器优化):

 end=array.length
 i=0
 :LOOP_START
 if [ i < end ] goto :LOOP_END
 increment i
 ... BODY_CODE
 :LOOP_END
Run Code Online (Sandbox Code Playgroud)

注意end是一个需要CPU寄存器的变量.这可能会调用代码中的附加寄存器交换,并且在语句中需要更昂贵的比较if语句.

只需我5美分

对于高级语言而言,可读性(促进可维护性)作为次要的性能改进更为重要.

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

从数组结束到开始的更快迭代导致可能不需要的反向序列.

后脚本

正如评论中所要求的那样:在减少之前或之后的评估中的差异--i和.i--i

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

 % i=10; echo "$((--i)) --> $i"
 9 --> 9
 % i=10; echo "$((i--)) --> $i"
 10 --> 9
Run Code Online (Sandbox Code Playgroud)

  • 在回答中看到post scriptum (2认同)

BBo*_*Bog 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上看到一个基准测试).


Pav*_*l P 13

我认为在JavaScript中说它i--更快是不合理的i++.

首先,它完全依赖于JavaScript引擎实现.

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

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

for(var i = array.length; i--; )
Run Code Online (Sandbox Code Playgroud)

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

for(var i = 0; i < array.length; i++)
Run Code Online (Sandbox Code Playgroud)

然后在每次迭代array.length都必须进行评估(更聪明的JavaScript引擎或许可以弄清楚循环不会改变数组的长度).即使它看起来像一个简单的语句,它实际上是一些由JavaScript引擎在引擎盖下调用的函数.

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

  • 关于如何评估`array.length`可能值得仔细考虑.当您参考它时,长度不会*计算*.(它只是[在创建或更改数组索引时设置的属性](http://ecma-international.org/ecma-262/5.1/#sec-15.4)).当有额外的成本时,这是因为JS引擎没有优化该名称的查找链. (3认同)

A.M*_*M.K 13

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

所以,你走了:

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

测试结果:这个 jsPerf "证明" ,arr.pop()实际上是迄今为止最快的循环.但是,专注于--i,i--,i++++i你在你的问题问,这里有jsPerf(他们是从多个jsPerf的,请参见下面的源)结果总结:

--i并且i--在Firefox中i--是相同的,而在Chrome中更快.

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

在Chrome和Firefox中arr.length,Chrome的速度显着提高了大约170,000 ops/sec.

没有显着差异,++ii++大多数浏览器AFAIK 更快,它在任何浏览器中都不会相反.

简短摘要: arr.pop()是迄今为止最快的循环; 对于特别提到的循环,i--是最快的循环.

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

我希望这回答了你的问题.


小智 12

这取决于阵列在内存中的位置以及访问该阵列时内存页面的命中率.

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


Jav*_*ier 10

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

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


pei*_*rix 6

你现在做的方式并没有更快(除了它是一个无限循环,我猜你打算做i--.

如果你想让它更快,请执行以下操作:

for (i = 10; i--;) {
    //super fast loop
}
Run Code Online (Sandbox Code Playgroud)

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

  • 是的,我已经测量过它,我并不是说更短的源使它更快,而是更少的操作使它更快。 (4认同)

sea*_*lea 6

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

顺便说一句,如果你能保证count永远>= 0,你可以简化为:

for (var i = count; i--;)
{
  // whatever
}
Run Code Online (Sandbox Code Playgroud)

  • @Brian Knoblauch:如果使用诸如"dec eax"(x86代码)之类的指令,则该指令会自动设置Z(零)标志,您可以立即测试该标志,而无需在其间使用另一个比较指令. (7认同)

Arv*_*iya 6

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

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

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


Dmi*_*aev 6

for(var i = array.length; i--; )速度不是很快.但是当你替换array.lengthsuper_puper_function(),可能会明显更快(因为它在每次迭代中被调用).这就是区别.

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

PS:i--不快i++.


Sal*_*ali 6

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

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

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

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

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

  1. 有一个var a = array.length;声明,以便每次循环时都不会计算其值
  2. 循环展开http://en.wikipedia.org/wiki/Loop_unwinding

但你必须明白,你可以获得的改善是如此微不足道,大多数你甚至不应该关心它.

我个人认为为什么会出现这种误解(Dec vs Inc)

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

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


Spy*_*yto 6

在jsbench上进行了比较.

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

for ( var i = 1; i <= array.length; i++ )
Run Code Online (Sandbox Code Playgroud)

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

for ( var i = 1, l = array.length; i <= l; i++ )
Run Code Online (Sandbox Code Playgroud)

.length当你申报时,你只评估一次i.在这一个:

for ( var i = array.length; i--; )
Run Code Online (Sandbox Code Playgroud)

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

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

for (i = values.length; i-- ;) {
  add( values[i] );
}
Run Code Online (Sandbox Code Playgroud)

循环内联代码:

var sum = 0;
for ( i = values.length; i-- ;) {
  sum += values[i];
}
Run Code Online (Sandbox Code Playgroud)

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


注意:随着浏览器越来越擅长内联简单函数,它实际上取决于代码的复杂程度.所以,优化前的配置文件,因为

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

但要记住:

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


Sal*_*n A 5

++vs.--无关紧要,因为 JavaScript 是一种解释型语言,而不是一种编译型语言。每条指令都翻译成一种以上的机器语言,你不应该关心血腥的细节。

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

您应该编写可读的代码。