285

I've heard this quite a few times. Are JavaScript loops really faster when counting backward? If so, why? I've seen a few test suite examples showing that reversed loops are quicker, but I can't find any explanation as to why!

I'm assuming it's because the loop no longer has to evaluate a property each time it checks to see if it's finished and it just checks against the final numeric value.

I.e.

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

34 回答 34

931

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

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

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

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

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

当您检查 if 时,您.length每次增加时都会进行评估。ii <= array.length

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

于 2012-10-30T10:12:39.713 回答
203

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

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

var i = arr.length; //or 10
while(i--)
{
  //...
}
于 2009-08-27T12:03:57.307 回答
132

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

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

[[对于像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.popor 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或其他复杂的图书馆功能(如果可用)。;)

于 2012-10-30T11:22:46.517 回答
35

i--一样快i++

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

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

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

于 2012-10-30T10:21:34.577 回答
23

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

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

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

编辑:

Milan Adamovsky 创建的最新基准可以在运行时针对不同的浏览器执行。

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

基准示例。

于 2012-10-30T15:18:13.410 回答
21

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

a++ < length

实际上编译为

a++
test (a-length)

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

于 2012-10-30T11:09:40.417 回答
15

简短的回答

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

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

适用于所有高级语言,并且大部分独立于 JavaScript 的使用。解释是最后一行的汇编代码。

详细解释

循环中可能会出现性能差异。背景是在汇编代码级别上,您可以看到 acompare 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 美分

对于高级语言,便于维护的可读性作为较小的性能改进更为重要。

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

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

后文

--i如评论中所问:和的区别在于递减之前或之后的i--评估。i

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

 % i=10; echo "$((--i)) --> $i"
 9 --> 9
 % i=10; echo "$((i--)) --> $i"
 10 --> 9
于 2012-10-31T09:50:37.230 回答
15

我在 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++vsi--优化,而在于不必在每次迭代时评估数组的长度(你可以在jsperf上查看基准测试)。

于 2012-10-30T12:02:35.997 回答
14

我认为说它比JavaScripti--更快是没有意义的。i++

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

其次,如果最简单的构造 JIT 并翻译成本地指令,那么i++vsi--将完全依赖于执行它的 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--可以考虑更快...

于 2012-10-30T18:20:05.930 回答
13

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

所以,给你:

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

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

--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。

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

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

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

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

于 2012-10-31T14:14:25.687 回答
12

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

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

于 2012-10-30T10:19:54.203 回答
10

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

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

于 2012-10-30T15:15:28.483 回答
7

The way you're doing it now isn't faster (apart from it being an indefinite loop, I guess you meant to do i--.

If you want to make it faster do:

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

of course you wouldn't notice it on such a small loop. The reason it's faster is because you're decrementing i while checking that it's "true" (it evaluates to "false" when it reaches 0)

于 2009-08-27T11:57:44.230 回答
7

It can be explained by JavaScript (and all languages) eventually being turned into opcodes to run on the CPU. CPUs always have a single instruction for comparing against zero, which is damn fast.

As an aside, if you can guarantee count is always >= 0, you could simplify to:

for (var i = count; i--;)
{
  // whatever
}
于 2009-08-27T11:58:14.027 回答
7

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

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

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

所以就你所见,任何环境下的性能都没有区别。

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

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

但你必须明白,你能获得的进步是如此微不足道,以至于你甚至不应该关心它。

我自己的看法为什么会出现这样的误解(Dec vs Inc)

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

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

于 2012-10-31T00:52:43.943 回答
7

在 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--; )

比较是隐式的,它发生在 decrementing 之前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. 你可以选择更好的数据结构

但要记住:

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

于 2015-01-22T11:19:58.407 回答
7

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

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

PS:i--不比i++.

于 2012-10-30T14:30:00.667 回答
6

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

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

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

于 2012-10-30T11:45:57.923 回答
5

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

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

您应该编写可读的代码。

于 2012-10-31T13:36:51.340 回答
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--) {...

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

于 2012-10-31T06:52:08.127 回答
4

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

我同意其他人所说的这些天没有什么不同。

于 2012-10-30T18:09:18.690 回答
3

In many cases, this has essentially nothing to do with the fact that processors can compare to zero faster than other comparisons.

This is because only a few Javascript engines (the ones in the JIT list) actually generate machine language code.

Most Javascript engines build an internal representation of the source code which they then interpret (to get an idea of what this is like, have a look near the bottom of this page on Firefox's SpiderMonkey). Generally if a piece of code does practically the same thing but leads to a simpler internal representation, it will run faster.

Bear in mind that with simple tasks like adding/subtracting one from a variable, or comparing a variable to something, the overhead of the interpreter moving from one internal "instruction" to the next is quite high, so the less "instructions" that are used internally by the JS engine, the better.

于 2009-10-08T08:36:26.970 回答
3

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

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

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

于 2012-10-30T12:54:06.627 回答
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--'

于 2012-10-31T10:40:01.943 回答
3

用非常简单的话

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

但是在这种情况下,当您进行增量操作时..处理器在每次变量增加 1 时评估 .length 并且在减少的情况下..特别是在这种情况下,它只会评估 .length 一次,直到我们得到 0。

于 2012-10-31T10:53:52.253 回答
3

i-- 或 i++ 消耗的时间并不多。如果深入了解 CPU 架构,++则速度会比 更快--,因为该--操作将执行 2 的补码,但它发生在硬件内部,因此这将使其速度更快,并且这些操作之间没有重大区别,++并且--这些操作也被认为是在 CPU 中消耗的时间最少。

for 循环运行如下:

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

所以,

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

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

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

会计算每个循环的长度,所以会消耗很多时间。

于 2012-10-31T09:02:37.390 回答
1

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

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

于 2009-08-27T11:58:44.243 回答
1

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

简单地说,与零的比较总是最快的比较

所以 (a==0) 实际上返回 True 比 (a==5) 更快

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

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

在向下循环中,您可能会说 where i >= 0 而是递减 i。

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

于 2009-08-27T12:08:46.300 回答
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;
}
于 2012-08-17T17:15:59.180 回答
1

编译器不会缓存 .length,因此比较 0 或 .length 没有区别吗?我想这对于您正在处理的编译器或解释器来说非常具体。

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

于 2012-10-31T05:44:36.347 回答
1

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

于 2012-10-30T10:13:01.313 回答
0

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

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

于 2011-12-15T23:04:09.947 回答
0

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

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

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

于 2009-08-27T12:08:10.637 回答
-2

减量模式的最快方式:

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

比...快:

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

Javascript微优化测试

于 2013-01-23T15:22:46.710 回答