反向循环真的更快吗?

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

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

即。

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

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

如果你想让它更快,请:

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

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

它可以解释为JavaScript(以及所有语言)最终被转换为在CPU上运行的操作码。cpu总是只有一条指令用于与0进行比较,这非常快。

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

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

回答这类问题的最好方法就是亲自去尝试。建立一个计算百万次迭代的循环,并以两种方式进行。对两个循环进行计时,并比较结果。

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

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

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

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

使用前缀增量操作符要快一些。对于后缀,编译器必须保留之前的值作为表达式的结果。

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

聪明的解释器或编译器会看到i++的结果没有被使用,也不会存储表达式的结果,但不是所有的js引擎都这样做。

爱死它了,很多分数但没有回答:D

简单地把比较放在零总是最快的比较

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

它很小且无关紧要,并且集合中有1亿行,因此它是可测量的。

例如,在循环中,你可能会说where I <= array。长度和I的增量

在下行循环中,你可能会说I >= 0,而不是减去I。

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

在许多情况下,这本质上与处理器比其他比较更快地比较0这一事实无关。

这是因为只有几个Javascript引擎 (JIT列表中的那些)实际上会生成机器语言代码。

大多数Javascript引擎构建源代码的内部表示,然后对其进行解释(要了解这是什么样子,请查看这个页面在Firefox的SpiderMonkey上底部附近)。一般来说,如果一段代码实际上做同样的事情,但导致更简单的内部表示,它将运行得更快。

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

我想为这个线程贡献最快的JavaScript循环,是跨浏览器的!与反向while循环相比,该循环的性能提高了500%以上。

我的博客:JavaScript中最快的循环

帮助别人避免头痛——投票吧!!

本页上最流行的答案在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;
}

并不是说i--i++快。实际上,它们都一样快。

在升序循环中花费时间的是为每个i计算数组的大小。在这个循环中:

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

当你声明i时,你只对.length求值一次,而对于这个循环

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

每次递增i时,当你检查i <= array.length

在大多数情况下,你根本不用担心这种优化

这只是一个猜测,但可能是因为处理器更容易将某个值与0 (i >= 0)进行比较,而不是与另一个值(i <Things.length)。

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

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

i--i++一样快

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

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

建议不要每次都计算数组的大小。对于大数组,可以看到性能下降。

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

a++ < length

实际编译为

a++
test (a-length)

所以它在编译时在处理器上花费的时间更长。

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

括号中 是我的信念,直到我最近测试了这个问题:

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

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

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 = (50000 x100000)操作,上升和下降之间没有区别如果测试是针对一个常量,如@alestanis所说。(JsPerf的结果有时不一致,但总的来说都是一样的:你不能有很大的区别。 所以--i恰好是一个相当“时髦”的东西。这只会让你看起来像一个更好的程序员。:) < / p >

另一方面,在5e9的情况下,当我走10秒时,它让我从12秒下降到2.5秒,当我走20秒时,它下降到2.1秒。它没有优化,而优化使事情缩短到不可估量的时间。:)(展开可以用我上面的方法或使用i++来完成,但这并没有在JavaScript中带来进步。)

总之:在面试中保留i--/i++++i/i++的差异,在可用时坚持使用array.forEach或其他复杂的库函数。;)

这并不依赖于--++符号,而是取决于你在循环中应用的条件。

例如:如果变量有一个静态值,那么循环会比每次循环都检查条件(比如数组的长度或其他条件)更快。

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

我在《Sublime Text 2》中看到了同样的建议。

如前所述,主要的改进不是在for循环的每次迭代中计算数组的长度。这是一种著名的优化技术,当数组是HTML文档的一部分时,在JavaScript中特别有效(为所有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++ vs i--的优化,而在于不必在每次迭代中计算数组的长度(你可以在jsperf上看到基准测试)。

好吧,我不了解JavaScript,它应该只是一个重新计算数组长度的问题,也许与关联数组有关(如果你只是递减,不太可能需要分配新条目——如果数组是密集的,也就是说。有人可能会为此进行优化)。

在低级汇编中,有一个循环指令,称为DJNZ(如果非零则递减和跳跃)。因此,递减和跳转都在一条指令中,使得它可能比INC和JL / JB略快(递增,如果小于则跳转/如果低于则跳转)。同样,与零比较比与其他数字比较简单。但所有这些都是边缘的,也取决于目标架构(可能会产生不同,例如智能手机上的Arm)。

我没想到这种低级别的差异会对解释语言产生这么大的影响,我只是在回答中没有看到DJNZ,所以我想分享一个有趣的想法。

for(var i = array.length; i--; )并不快多少。但是当你用super_puper_function()替换array.length时,可能会更快地得到显著(因为它在每次迭代中都被调用)。这就是区别。

如果你打算在2014年改变它,你不需要考虑优化。如果你打算用“搜索&替换”,你不需要考虑优化。如果你没有时间,你不需要考虑优化。但是现在,你有时间考虑一下。

注:i--并不比i++快。

我最后一次为它烦恼是在编写6502程序集时(8位,是的!)最大的收获是大多数算术操作(特别是减数)更新了一组标志,其中一个是Z,“达到零”指示符。

因此,在循环的最后,你只执行了两条指令:DEC(减量)和JNZ(跳,如果不为零),不需要比较!

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

我同意其他人的看法,他们说现在没有什么不同。

我认为在JavaScript中说i--i++更快是没有意义的。

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

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

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

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

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

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

然后在每次迭代中array.length都必须被计算(更聪明的JavaScript引擎也许可以计算出循环不会改变数组的长度)。尽管它看起来像一个简单的语句,但它实际上是JavaScript引擎在底层调用的某个函数。

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

长话短说:在JavaScript中执行此操作绝对没有区别。

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

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

所以就你所看到的,在任何环境下,两者的性能没有区别。

如果你想提高脚本的性能,你可以尝试做以下事情:

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

但你必须明白,你所能获得的改善是如此微不足道,大多数情况下你甚至不应该关心它。

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

很久很久以前,有一个常见的机器指令,DSZ(减量和跳过零)。用汇编语言编程的人使用这条指令来实现循环,以保存寄存器。现在这个古老的事实已经过时了,而且我非常确定使用这个伪改进在任何语言中都不会得到任何性能改进。

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

编译器不会缓存。length,因此它没有区别,如果你比较0或。length?我想这对于你正在处理的编译器或解释器是非常特定的。

我想说的是,如果你使用的是一个优化的编译器或解释器,那么你不应该担心这个问题,这是语言开发人员应该担心的问题。

有时,对代码的编写方式做一些非常小的更改,可能会对代码的实际运行速度产生很大的影响。一个微小的代码更改可能会对执行时间产生很大影响的地方是我们有一个处理数组的for循环。当数组是网页上的元素(如单选按钮)时,这种改变的影响最大,但即使数组是Javascript代码的内部,也值得应用这种改变。

编写for循环来处理数组的常规方法如下:

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

这样做的问题是使用myArray计算数组的长度。长度需要时间,并且我们编写循环的方式意味着必须在每次循环中执行此计算。如果数组包含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--) {...

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

i——或i++占用的时间并不多。如果你深入到CPU架构内部,++--更快,因为--操作将做2的补,但它发生在硬件内部,所以这将使它更快,并且++--之间没有重大区别,而且这些操作被认为是CPU消耗的时间最少的。

for循环是这样运行的:

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

所以,

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

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

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

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

简短的回答

对于普通代码,特别是在像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

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

只是我的5美分

对于高级语言来说,可读性(有助于可维护性)作为一种较小的性能改进更为重要。

通常,数组从头到尾中的经典迭代更好。

从数组结束开始快速迭代会导致可能不需要的反向序列。

后记

正如在评论中问到的:--ii--的区别在于i在递减之前或之后的求值。

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

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

首先,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. 仅对少数语言(JavaScript引擎)缓慢,因为编译器的优化。最好的方法是使用'<'代替'<='(或'=')和'——i'代替'i——'

简单地说

“i——和i++。实际上,它们都需要相同的时间。”

但在这种情况下,当你进行增量操作。处理器计算.length每个时间变量都加1,如果减量..特别是在这种情况下,它只计算一次。length直到得到0。

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

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

您应该编写可读的代码。

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

所以,你看:

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

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

在Firefox中--ii--是相同的,而在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++快,AFAIK,在任何浏览器中都不会相反。

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

__abc0 __abc1, __abc2

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

递减模式的最快方式:

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

速度比:

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

Javascript微优化测试

我做了一个jsbench的比较

正如alestani指出的那样,在升序循环中需要花费时间的一件事是,对于每次迭代,计算数组的大小。在这个循环中:

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

每次增加i时,都计算.length。在这篇文章中:

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

当你声明i时,你只对.length求值一次。在这篇文章中:

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. 您可以选择更好的数据结构

但是请记住:

代码是为了让人阅读而写的,只是顺便让机器执行。