循环反向真的更快吗?


268

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

我假设这是因为循环不再需要在每次检查是否完成时都求值一个属性,而只需要检查最终的数值即可。

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

6
呵呵。这将无限期地发生。尝试我
StampedeXV

5
向后for循环更快,因为不必从对象定义或获取上限(嘿,下限)循环控制变量。它是一个恒定的零。
乔什·斯托多拉

88
没有真正的区别。本机循环构造总是非常快。不用担心他们的表现。
James Allardice 2012年

24
@Afshin:对于此类问题,请向我们显示您所引用的文章。
Lightness Races in Orbit

22
对于非常低端的设备和电池供电的设备,主要存在区别。区别在于使用i--循环结束时与0进行比较,而使用i ​​++则对数字> 0进行比较。我相信性能差异约为20纳秒(类似于cmp ax,0 vs. cmp ax之类的东西) ,bx)-没什么,但是如果您每秒循环数千次,为什么不每次都获得20纳秒的增益:)
luben 2012年

Answers:


902

不是i--比快i++。实际上,它们的速度都一样快。

在递增循环中需要花费时间的是,每个循环都需要评估i数组的大小。在此循环中:

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

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

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

当您检查是否.length每次递增时i,您都进行评估i <= array.length

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


23
是否值得为array.length引入变量并在for循环的头部使用它?
ragatskynet 2012年

39
@ragatskynet:不,除非您要设置基准并想提出一点。
2012年

29
@ragatskynet这取决于:评估.length一定次数或声明和定义新变量会更快吗?在大多数情况下,这是过早的(也是错误的)优化,除非您的length评估非常昂贵。
alestanis 2012年

10
@ Dr.Dredel:这不是比较-是评估。0比评估更快array.length。好吧,据推测。
Lightness Races in Orbit

22
值得一提的是,我们正在谈论诸如Ruby,Python之类的解释语言。诸如Java之类的编译语言在编译器级别进行了优化,可以“平滑”这些差异,以至于是否.length声明是无关紧要的for loop
哈里斯·克拉吉纳

198

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

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

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

好的:)但是没有向后测试“ for”循环...但是peirix或searlea提到的for循环应该与以“ i--”为条件的“ while”循环大致相同。那是测试最快的循环。
SvenFinke

11
有趣,但是我想知道递减是否会更快。由于它不必存储i的中间值。
tvanfosson

如果我没记错的话,我的硬件课程教授说,测试0或不为0是最快的“计算”。在while(i--)中,测试始终是0的测试。也许这就是为什么这样做最快的原因?
StampedeXV

3
@tvanfosson,如果您先递减,--i则必须var current = arr[i-1];在循环内使用,否则它将被关闭...
DaveAlger

2
我听说i--可以比--i更快,因为在第二种情况下,处理器需要递减然后对新值进行测试(指令之间存在数据相关性),而在第一种情况下,处理器可以测试现有值并在一段时间后减小该值。我不确定这是否适用于JavaScript或仅适用于非常低级的C代码。
marcus

128

我试图用这个答案来概括一下。

在最近测试该问题之前,以下是方括号中的想法:

[[就C / C ++之类的低级语言而言,代码被编译为当变量为零(或非零)时,处理器具有特殊的条件跳转命令。 另外,如果你在乎这么多的优化,你可以去代替,因为是单处理器的命令而手段。]
++ii++++ii++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功能(取决于浏览器),使其使用最新的最佳技巧即可!:) @AMK表示在特殊情况下值得使用array.poparray.shift。如果这样做,请将其放在窗帘后面。的最大矫枉过正是添加选项,forEach选择循环算法。]

此外,对于低级语言,最佳实践是尽可能使用一些智能库功能来执行复杂的循环操作。

这些库还可以将事物(多线程)放在背后,并且专业的程序员可以使它们保持最新。

我进行了更多的检查,结果发现在C / C ++中,即使对于5e9 =(50,000x100,000)运算,如果对@alestanis这样的常量进行测试,则向上和向下之间也没有区别。(JsPerf的结果有时不一致,但是总的说来是一样的:您不可能有什么大的不同。)
因此,--i恰好是“正题”。它只会使您看起来像一个更好的程序员。:)

另一方面,在这种5e9情况下展开时,它使我从10秒时的12秒降低到2.5秒,而到20秒时则从2.1秒降低到2.1秒。这是没有优化的,而优化使事情降到了不可估量的很少的时间。:)(可以通过上面的方式或使用来完成展开操作i++,但这无法使JavaScript发挥作用。)

总而言之:保留i--/ i++++i/i++在面试中不同,坚持使用array.forEach或其他复杂的库功能。;)


18
关键字是“可以”。您展开的循环可能也比原始循环慢。优化时,请始终进行测量,以使您确切了解更改所产生的影响。
2012年

1
@jalf确实是+1。不同的开环长度(> = 1)效率不同。这就是为什么在可能的情况下将这项工作留给库更方便的原因,更不用说浏览器在不同的体系结构上运行,因此,如果他们决定怎么做可能会更好array.each(...)。我认为他们不会尝试对普通的for循环进行循环。
巴尼(Barney)2012年

1
@BarnabasSzabolcs这个问题专门针对JavaScript,而不是C或其他语言。在JS那里一个区别,请参阅我的回答如下。虽然不适用于该问题,但+1很好的答案!
AMK 2012年

1
然后您去了- +1拿到了Great Answer徽章。真是个好答案。:)
Rohit Jain 2012年

1
APL / A ++也不是一种语言。人们用这些来表达他们对特定语言不感兴趣,但是对这些语言共享的东西不感兴趣。
巴尼(Barney)2012年

33

i-- 快如 i++

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

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

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


6
您在这里显然是错误的。现在,循环控制需要额外的内部变量(i和up),并且取决于JavaScript引擎,这可能会限制优化代码的潜力。JIT会将简单的循环转换为直接的机器操作码,但是如果循环中的变量太多,则JIT将无法进行最佳优化。通常,它受JIT使用的体系结构或cpu寄存器的限制。初始化一次,然后使用最干净的解决方案,这就是为什么在各处都推荐这样做的原因。
Pavel P

通用解释器/编译器的优化器将消除附加变量。关键是“比较0”-有关更多说明,请参见我的答案。
H.-Dirk Schmitt,2012年

1
最好在循环中放“上”:for (var i=0, up=Things.length; i<up; i++) {}
thdoan 2015年

22

由于您对此主题感兴趣,因此请查看Greg Reimer的有关JavaScript循环基准的博客文章,“用JavaScript编写循环的最快方法是什么?

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

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

编辑:

米兰Adamovsky创建一个更近的基准可以在运行时进行不同的浏览器。

为了在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;
}

基准示例。


5
该职位现在已有4岁。考虑到JavaScript引擎的更新速度(ha!),大多数建议可能已过时-这篇文章甚至没有提到Chrome,而它是闪亮的V8 JavaScript引擎。
Yi Jiang

是的,我想念那个细节,谢谢你指出。当时的事件在循环上--i或i ++之间没有太大区别。
dreamcrash 2012年

19

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

a++ < length

实际上被编译为

a++
test (a-length)

因此,编译时它在处理器上花费的时间更长。


15

简短答案

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

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

适用于所有高级语言,并且基本上独立于JavaScript的使用。解释是最后一行生成的汇编代码。

详细说明

循环中可能会出现性能差异。背景是,在汇编代码级别上,您可以看到a 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寄存器的变量。这可能会在代码中调用额外的寄存器交换,并且在语句中需要更昂贵的compareif语句。

就是我的五分钱

对于高级语言而言,提高可读性和可维护性更为重要,这是对性能的次要改进。

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

从数组末尾到起点的更快迭代会导致可能不需要的反向序列。

投稿后

至于问评论:的差异--ii--在评价i减量之前或之后。

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

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

1
1+很好的解释。只是一个问题有点超出范围,请你解释一下之间的区别--ii--也?
Afshin Mehrabani 2012年

14

我在Sublime Text 2中看到了相同的建议。

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

例如,

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上看到基准测试)。


1
在这里,我必须对计算一词表示例外。请参阅我对Pavel的回答的评论。ECMA规范指出,引用数组长度时不会计算出来。
kojiro 2012年

“评估”会是更好的选择吗?有趣的是,我还不知道
BBog 2012年

通用解释器/编译器的优化器将消除附加变量。同样适用于array.length评估。关键是“比较0”-有关更多说明,请参见我的答案。
H.-Dirk Schmitt

@ H.-DirkSchmitt您的常识性的回答之外,长期在JavaScript的历史编译器确实没有优化掉的查找链的性能开销。AFAIK V8是第一个尝试的。
kojiro 2012年

就像kojiro所说的那样,@ H.-DirkSchmitt也是众所周知的既定技巧。即使它在现代浏览器中不再相关,也不会使其过时。此外,通过引入一个新的length变量或OP问题中的技巧来做到这一点,imo仍然是最好的方法。这只是做了一件聪明事,好的做法,我不认为这有什么关系的事实,编译器进行了优化,照顾的东西在一个糟糕的方式在JS经常做
BBog

13

我认为说这i--比使用i++JavaScript 更快是没有道理的。

首先,它完全取决于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--可以被认为“更快”)是因为JavaScript引擎只需要分配一个内部变量来控制循环(变量即可var i)。如果将其与array.length或其他变量进行比较,则必须有多个内部变量来控制循环,并且内部变量的数量是JavaScript引擎的有限资产。循环中使用的变量越少,JIT进行优化的机会就越大。这就是为什么i--可以考虑更快...


3
这也可能是有关如何值得谨慎的措辞array.length进行了评价。引用时不会计算长度。(这只是在创建或更改数组索引时设置的属性)。如果需要支付额外费用,那是因为JS引擎尚未优化该名称的查找链。
kojiro 2012年

好吧,不确定Ecma规范怎么说,但是了解不同JavaScript引擎的一些内部知识并不容易,getLength(){return m_length; }因为其中涉及一些内部管理。但是,如果您试着向后思考:编写需要实际计算长度的数组实现会非常巧妙:)
Pavel P

ECMA规范要求 length属性已经计算出来。length每当添加或更改数组索引属性时,都必须立即更新。
kojiro 2012年

我要说的是,如果您考虑考虑,很难违反规范。
Pavel P

1
在这方面,x86就像ARM。 dec/jnzinc eax / cmp eax, edx / jne
彼得·科德斯

13

由于没有其他答案似乎可以回答您的特定问题(超过一半的答案显示了C示例并讨论了底层语言,因此您的问题是针对JavaScript的),因此我决定编写自己的答案。

所以,你去:

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

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

--ii--Firefox中相同,而i--在Chrome中更快。

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

在Chrome和Firefox中arr.length,Chrome 的缓存速度明显提高了170,000个操作/秒。

没有显着差异,++i它比i++大多数浏览器AFAIK 都快,在任何浏览器中都没有反之。

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

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

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


1
看来您的pop测试似乎非常快,因为据我所知,它在大多数循环中将数组大小减小为0。但是为了确保事情公平,我已经设计了这个jsperf来以与每个测试相同的方式创建数组-似乎表明.shift()这是我的少数浏览器的赢家-但这不是我期望的:) jsperf.com / compare-different-types-of-looping
Pebbl 2012年

因是唯一提到的一个而被赞扬++i:D
jaggedsoft

12

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

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


1
如果仅OP询问为什么以不同顺序遍历同一矩阵会花费不同的时间
。.– dcow 2012年

1
考虑到在操作系统中其内存管理基于分页,当进程需要不在缓存页面中的数据时,会在OS中发生页面错误,因此必须将目标页面带到CPU缓存中并替换为另一个页面,因此,导致处理开销比目标页面在CPU缓存中花费更多的时间。假设我们定义了一个大数组,其中的每一行都大于页面OS的大小,并且我们按行顺序进行访问,在这种情况下,页面错误率增加了,结果要比按列顺序访问该数组要慢。
Akbar Bayati 2012年

页面错误与高速缓存未命中不是一回事。如果您的内存已分页到磁盘,则只会出现页面错误。按顺序将多维数组存储在内存中的顺序访问速度更快,这是由于缓存位置(在获取缓存行时使用其所有字节),而不是因为页面错误。(除非您的工作集对于正在使用的计算机而言太大。)
Peter Cordes

10

我上次为它烦恼的时间是在编写6502汇编语言时(8位,是的!)。最大的好处是大多数算术运算(尤其是递减)更新了一组标志,其中一个标志是Z“到达零”指示器。

因此,在循环结束时,您仅执行了两条指令:(DEC减量)和JNZ(如果不为零则跳转),不需要比较!


在JavaScript的情况下,显然它不适用(因为它在没有此类操作码的CPU上运行)。i--vs 背后的真正原因很可能i++是前者在循环范围内没有引入额外的控制变量。请在下面查看我的答案...
Pavel P

1
是的,这确实不适用;但这是一种非常常见的C风格,对于习惯了这种风格的我们来说,它的确看起来更干净。:-)
哈维尔

在这方面,x86和ARM都像6502。 dec/jnz而不是inc/cmp/jnex86的情况。您不会看到空循环运行得更快(两者都会使分支吞吐量饱和),但是略微减少计数会减少循环开销。当前的Intel HW预取器也不会受到降序使用的内存访问模式的困扰。我认为较旧的CPU可以跟踪4个后向流和6或10个前向流,即IIRC。
彼得·科德斯

7

最终可以通过JavaScript(和所有语言)将其解释为可在CPU上运行的操作码来解释。CPU始终只有一条指令与零进行比较,这真是太快了。

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

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

1
较短的源代码并不一定意味着它会更快。你测量了吗?
Greg Hewgill,2009年

糟糕,我错过了这个。头上有指甲。
罗伯特

1
我想看看汇编源代码与0的比较有所不同。已经有好几年了,但是有一次我做了很多汇编代码,而且我终生无法想到一种以与之前一样快的方式对0进行比较/测试的方法对于任何其他整数。但是,您所说的确实正确。沮丧的是我不知道为什么!
Brian Knoblauch

7
@Brian Knoblauch:如果使用“ dec eax”(x86代码)等指令,则该指令会自动设置Z(零)标志,您可以立即对其进行测试,而不必在它们之间使用其他比较指令。
2009年

1
但是,在解释Javascript时,我怀疑操作码是否会成为瓶颈。更少的令牌很可能意味着解释器可以更快地处理源代码。
托德·欧文

6

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

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

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


多个毫微秒的循环可能会变成几秒钟……优化您的时间绝不是一个坏主意
Buksy 2012年

6

for(var i = array.length; i--; )不会很快。但是,当你更换array.length使用super_puper_function(),这可能是显著快(因为它被称为在每次迭代)。就是这样。

如果要在2014年进行更改,则无需考虑优化。如果要通过“搜索并替换”进行更改,则无需考虑优化。如果没有时间,则无需考虑优化。但是现在,您有时间考虑一下。

PS:i--不比快i++


6

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

首先,您可以自己对其进行测试:

您不仅可以测试和运行任何JavaScript库中的任何脚本,而且还可以访问所有先前编写的脚本,还可以查看不同平台上不同浏览器中执行时间之间的差异。

因此,据您所知,在任何环境下,性能之间都没有区别。

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

  1. 有一个var a = array.length;声明,这样您就不会在循环中每次都计算它的值
  2. 循环展开http://en.wikipedia.org/wiki/Loop_unwinding

但是您必须了解,可以获得的改进将是微不足道的,以至于大多数情况下您甚至都不在乎。

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

很久很久以前,有一条通用的机器指令DSZ(减量和零位跳过)。用汇编语言编程的人使用此指令来实现循环以保存寄存器。现在,这个古老的事实已经过时了,我敢肯定,使用这种伪改进,您不会在任何语言上获得任何性能改进。

我认为,这种知识在我们时代传播的唯一途径是您阅读他人的个人密码。看到这样的构造,并询问为什么要实现,在这里给出答案:“由于与零相比,它提高了性能”。您对您的同事的高级知识感到困惑,并认为使用它会变得更加聪明:-)


有趣,但是对于在win7上在firefox 16.0.2下运行的测试,递减循环的速度要慢29%...编辑:忽略这一点。重复的测试没有定论,随后的运行中我的测试结果中出现了令人惊讶的噪声。不太清楚为什么。

是的,我尝试通过关闭其他所有功能并仅运行测试来解决这一问题。仍然得到了怪异的结果。很奇怪。

我认为您错过了为什么在JavaScript中将零归为更好的问题。主要原因是这种方式仅由一个变量控制循环执行,例如,优化器/ JITer具有更大的改进空间。使用array.length并不一定会导致性能下降,仅仅是因为JS虚拟机足够聪明,可以确定循环的主体是否修改了数组。请参阅下面的答案。
帕维尔P

1
挑剔:这个古老的事实(汇编语言优化)不是过时的,只是奥秘的。因为您不需要知道,除非您确实知道。:-)
哈维尔

6

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

如果可以内联代码,而不用调用函数而不牺牲易读性,则可以使循环快一个数量级!


注意:随着浏览器越来越擅长内联简单函数,它实际上取决于代码的复杂程度。因此,在优化之前先进行分析,因为

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

但要记住:

代码是供人们阅读的,只是偶然地供机器执行。


+1为该答案和基准。我已经添加了forEach测试,并将基准重新设计为可在浏览器以及Node中运行的独立文件。jsfiddle.net/hta69may/2。对于节点“反向循环,隐式比较,内联代码”是最快的。但是在FF 50中进行的测试显示了令人惊讶的结果:不仅计时减少了将近10倍(!),而且两次“ forEach”测试都与“反向循环”一样快。也许Node家伙应该使用Mozilla的JS引擎而不是V8?:)
Fr0sT

4

您现在的操作方式并没有更快(除了不确定的循环之外,我想您打算这样做)i--

如果要使其更快,请执行以下操作:

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

当然,您不会在这么小的循环中注意到它。之所以更快,是因为您在检查i为“ true”时递减i(当它达到0时其值为“ false”)


1
您是否缺少分号? (i = 10; i--;)
09年

是的,我已解决错误。如果我们要挑剔,我会指出您在我之后忘记了半冒号!嘿。
djdd87

为什么会更快?更短的来源并不意味着它将更快。你测量了吗?
2009年

4
是的,我已经对其进行了测量,并不是说更短的源可以使其更快,但是更少的操作可以使其更快。
peirix

4

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

编码for循环以处理数组lis的常规方法如下:

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--) {...

这段代码在开始时仍然只评估一次数组的大小,而不是将循环计数器与变量进行比较,而是将其与常量进行比较。因为常量比变量更有效地访问,并且由于赋值语句比以前少,所以我们的第三版代码现在比第二版稍微更高效,并且比第一个版本更高效。


4

++vs. --无关紧要,因为JavaScript是一种解释性语言,而不是一种编译语言。每条指令翻译成一种以上的机器语言,因此您不必在意这些细节。

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

您应该编写可读的代码。


3

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

这是因为只有少数Javascript引擎(JIT列表中的Java引擎)实际生成机器语言代码。

大多数Javascript引擎都会构建源代码的内部表示形式,然后对其进行解释(要了解其本质,请在Firefox的SpiderMonkey该页面底部附近浏览)。通常,如果一段代码实际上做同样的事情,但是导致内部表示更简单,那么它将运行得更快。

请记住,对于简单的任务,例如将一个变量与变量进行加/减或将变量与某物进行比较,解释器从一个内部“指令”转移到下一个“指令”的开销非常高,因此, JS引擎内部使用的效果更好。


3

好吧,我不了解JavaScript,这实际上应该只是重新评估数组长度的问题,并且可能与关联数组有关(如果仅递减,则不太可能需要分配新条目-如果阵列是密集的,也就是说,有人可以对此进行优化)。

在低级汇编中,有一个称为DJNZ的循环指令(如果非零,则递减和跳转)。因此,减量和跳转全部集中在一条指令中,使其可能比INC和JL / JB稍快一些(增量,如果小于则跳转,如果小于则跳转)。同样,与零比较比与另一个数字比较容易。但是,所有这些实际上都是微不足道的,并且还取决于目标体系结构(例如,可能会影响智能手机中的Arm)。

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


作为记录,DJNZ是8051(z80)ISA中的一条指令。x86 dec/jnz代替inc/cmp/jne,显然arm也有类似的东西。我很确定这不是Javascript差异的原因。从循环条件中获取更多信息到评估结果。
彼得·科德斯

3

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

我同意那些说这几天没有影响的人。


3

简单来说

“ i--和i ++。实际上,它们都花费相同的时间”。

但是在这种情况下,当您进行增量操作时,处理器会在每次变量增加1时评估.length,在递减的情况下。尤其是在这种情况下,它将仅评估一次.length直到得到0。


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

由于编译器的优化,PS Slow仅对某些语言(JavaScript引擎)较慢。最好的方法是使用'<'代替'<='(或'=')和'--i'代替'i--'


3

i或i ++并没有花费很多时间。如果深入研究CPU体系结构,++则它的速度要快于--,因为该--操作将完成2的补数,但是它发生在硬件内部,因此这将使其变得快速,并且,++并且--这些操作之间的主要区别都不会被考虑在内。在CPU中消耗的时间最少。

for循环运行是这样的:

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

所以,

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

一开始只计算一次数组长度,这不是很多时间,但是

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

将在每个循环中计算长度,因此将花费大量时间。


var i = Things.length - 1; i >= 0; i--也会计算长度1倍。
德米特里(Dmitry)2012年

1
我不确定“ --运算将执行2的补码”是什么意思,但是我认为这意味着它会否定某些东西。不,它在任何体系结构上都不会否定任何东西。减1就像加1一样简单,您只需要制作一个借用而不是带进的电路。
奥拉西(Olathe)

1

回答此类问题的最佳方法是实际尝试。设置一个可以计算一百万次迭代的循环,并以两种方式进行。为两个循环计时,并比较结果。

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


4
那仍然无法回答他关于为什么更快的问题。他只是获得了一个基准,却不知道为什么会这样……
peirix

3
确实如此。但是,如果不知道每个浏览器中每个Javascript引擎的确切实现,几乎不可能回答“为什么”部分。这里的许多答案都涉及一些轶事建议,例如“使用先减量而不是后减量”(一种C ++优化技巧)和“比较零”,这在本机代码编译语言中可能是正确的,但Javascript远远不是裸露的。中央处理器。
Greg Hewgill

4
-1我完全不同意最好的方法是尝试一下。有两个示例不能代替集体知识,而这就是此类论坛的全部重点。
克里斯多夫

1

喜欢它,很多痕迹,但没有答案:D

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

因此(a == 0)实际上比(a == 5)返回True更快

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

即在循环上,您可能会说我<= array.length并在递增i

在下降循环中,您可能会说i> = 0并递减i。

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


所陈述的问题没有答案,因为Javascript引擎各不相同,答案完全取决于要在其上进行测量的浏览器。
2009年

不,从根本上来说,与零的比较是最快的。尽管您的陈述也是正确的,但与零进行比较的黄金法则是绝对的。
罗伯特

4
这是真的只是如果编译器选择做出优化,这肯定是不保证的。除了常量的值外,编译器可能会为(a == 0)和(a == 5)生成完全相同的代码。如果比较的两端都是变量(从CPU的角度来看),则CPU与0的比较不会比与任何其他值更快的比较。通常,只有本机代码编译器才有机会在此级别进行优化。
2009年

1

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

此页面上最流行的答案不适用于Firefox 14,并且未通过jsLinter。“ while”循环需要比较运算符,而不是赋值。它确实适用于chrome,safari,甚至ie。但是死于Firefox。

这已破了!

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

这将起作用!(在Firefox上运行,通过jsLinter)

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

2
我只是在Firefox 14上尝试过,但效果很好。我从生产库中看到了一些示例,这些示例while (i--)在许多浏览器中使用并可以在其中运行。您的考试可能会有些奇怪吗?您是否在使用Beta版本的Firefox 14?
马特·布朗

1

这只是一个猜测,但这也许是因为处理器比较具有0(i> = 0)而不是具有另一个值(i <Things.length)的值比较容易。


3
+1其他人没有投票。尽管对.length的重复求值比实际的inc / decrement要麻烦得多,但是对循环结束的检查可能很重要。我的C编译器在循环上发出了一些警告,例如:“注释#1544-D:(ULP 13.1)检测到循环递增计数。建议循环递减计数,因为检测零更容易”
harper 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.