循环反向真的更快吗?

Are loops really faster in reverse?

提问人:djdd87 提问时间:8/27/2009 最后编辑:dreamcrashdjdd87 更新时间:2/9/2021 访问量:144887

问:

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

我假设这是因为循环不再需要在每次检查属性时都计算属性以查看它是否完成,而只是根据最终数值进行检查。

J.F.公司

for (var i = count - 1; i >= 0; i--)
{
  // count is only evaluated once and then the comparison is always on 0.
}
JavaScript 性能 循环 for-loop while-loop

评论

6赞 StampedeXV 8/27/2009
呵呵。这将无限期地发生。试试我--
6赞 Josh Stodola 8/28/2009
向后循环速度更快,因为上界(呵呵,下界)循环控制变量不必定义或从对象中获取;它是一个常数零。for
92赞 James Allardice 10/30/2012
没有真正的区别。原生循环结构总是非常快。不要担心他们的表现。
25赞 Lightness Races in Orbit 10/30/2012
@Afshin:对于这样的问题,请向我们展示您提到的文章。
23赞 luben 10/31/2012
对于非常低端和电池供电的设备来说,差异主要很重要。不同之处在于,使用 i-- 您将循环结束时与 0 进行比较,而使用 i++ 您将与数字 > 0 进行比较。我相信性能差异大约是 20 纳秒(类似于 cmp ax,0 与 cmp ax,bx)——这没什么,但如果你每秒循环数千次,为什么不为每:)增加 20 纳秒

答:

8赞 peirix 8/27/2009 #1

你现在做这件事的方式并不快(除了它是一个无限循环,我猜你是想做的.i--

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

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

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

评论

1赞 searlea 8/27/2009
你错过了分号吗?(i = 10; i--;)
0赞 djdd87 8/27/2009
是的,是的,我修复了错误。如果我们要挑剔,我会指出你忘记了你的分号后面的 i--!嘿。
0赞 Greg Hewgill 8/27/2009
为什么会更快?较短的来源并不意味着它会更快。你测量过吗?
4赞 peirix 8/27/2009
是的,我已经测量过了,我并不是说更短的来源会让它更快,但更少的操作会让它更快。
0赞 mindplay.dk 4/12/2021
这里有一个基准测试,展示了这种差异 - jsbench.me/1cknepoalw/1
7赞 searlea 8/27/2009 #2

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

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

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

评论

1赞 Greg Hewgill 8/27/2009
更短的源代码并不一定意味着它会更快。你测量过吗?
0赞 Robert 8/27/2009
哎呀,我错过了这个。钉在头上,伙计。
1赞 Brian Knoblauch 8/27/2009
我想看看与 0 相比不同的程序集源。已经有几年了,但有一次我做了大量的汇编编码,我一辈子都想不出一种方法来对 0 进行比较/测试,而这种方式对于任何其他整数来说都不能同样快。然而,你说的确实是真的。很沮丧,我不知道为什么!
7赞 Greg Hewgill 8/27/2009
@Brian Knoblauch:如果您使用诸如“dec eax”(x86 代码)之类的指令,则该指令会自动设置 Z(零)标志,您可以立即对其进行测试,而无需在两者之间使用其他比较指令。
1赞 Todd Owen 8/27/2009
不过,在解释 Javascript 时,我怀疑操作码会成为瓶颈。更有可能的是,更少的令牌意味着解释器可以更快地处理源代码。
1赞 Greg Hewgill 8/27/2009 #3

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

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

评论

4赞 peirix 8/27/2009
这仍然不能回答他为什么更快的问题。他只是得到一个基准,不知道为什么会这样......
3赞 Greg Hewgill 8/27/2009
这是真的。然而,如果不知道每个浏览器中每个 Javascript 引擎的确切实现,几乎不可能回答“为什么”部分。这里的很多答案都围绕着轶事建议,例如“使用predecrement而不是postdecrement”(C++优化技巧)和“与零进行比较”,这在原生代码编译语言中可能是正确的,但Javascript与CPU的裸机相去甚远。
4赞 Christophe 1/31/2012
-1 我完全不同意最好的方法是尝试一下。几个例子并不能取代集体知识,这就是像这样的论坛的全部意义所在。
208赞 Tom Ritter 8/27/2009 #4

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

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

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

评论

0赞 SvenFinke 8/27/2009
不错的:)但是没有经过反向“for”循环测试......但是 peirix 或 searlea 提到的 for 循环应该与以“i--”为条件的“while”循环几乎相同。这是测试中最快的循环。
12赞 tvanfosson 8/27/2009
有趣的是,但我想知道预减减是否会更快。因为它不必存储 i 的中间值。
0赞 StampedeXV 8/27/2009
如果我没记错的话,我的硬件课程教授说,测试 0 或不 0 是最快的“计算”。在 while(i--) 中,检验始终是 0 的检验。也许这就是为什么这是最快的?
4赞 DaveAlger 8/1/2012
@tvanfosson如果你预先减级,那么你必须在循环内部使用,否则它会关闭一个......--ivar current = arr[i-1];
2赞 marcus 5/19/2013
我听说 i-- 可以比 --i 更快,因为在第二种情况下,处理器需要递减,然后针对新值进行测试(指令之间存在数据依赖关系),而在第一种情况下,处理器可以测试现有值并在一段时间后递减该值。我不确定这是否适用于 JavaScript 或仅适用于非常低级的 C 代码。
1赞 Robert 8/27/2009 #5

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

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

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

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

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

在向下循环中,您可能会说 i >= 0 的位置,然后递减 i。

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

评论

0赞 Greg Hewgill 8/27/2009
如前所述,这个问题没有答案,因为 Javascript 引擎都是不同的,答案取决于您在哪个浏览器上测量它。
0赞 Robert 8/27/2009
不,从根本上说,与零的比较是最快的。虽然你的陈述也是正确的,但与零进行比较的黄金法则是绝对的。
4赞 Greg Hewgill 8/27/2009
只有当编译器选择进行这种优化时,这才是正确的,这当然不能保证。编译器可能会为 (a==0) 和 (a==5) 生成完全相同的代码,但常量的值除外。如果比较的两边都是变量(从 CPU 的角度来看),则 CPU 与 0 的比较速度不会比与任何其他值的比较快。通常,只有本机代码编译器才有机会在此级别进行优化。
3赞 Artelius 10/8/2009 #6

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

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

大多数 Javascript 引擎都会构建源代码的内部表示,然后对其进行解释(要了解这是什么样的,请查看 Firefox 的 SpiderMonkey 页面底部附近的内容)。通常,如果一段代码几乎做同样的事情,但导致更简单的内部表示,它会运行得更快。

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

1赞 mrbinky3000 8/18/2012 #7

帮助他人避免头痛---对此投赞成票!!

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

这坏了!

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

这将起作用!(在 firefox 上工作,传递 jsLinter)

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

评论

2赞 Matt Browne 1/15/2013
我刚刚在 Firefox 14 上尝试过,效果很好。我看过来自生产库的示例,这些库使用并跨许多浏览器工作。你的测试会不会有什么奇怪的地方?您使用的是 Firefox 14 的测试版吗?while (i--)
946赞 alestanis 10/30/2012 #8

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

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

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

当您声明 时,您只计算一次,而对于此循环.lengthi

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

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

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

评论

32赞 ragklaat 10/30/2012
是否值得为 array.length 引入一个变量并在 for 循环的头部使用它?
45赞 Jon 10/30/2012
@ragatskynet:不,除非你正在建立一个基准,并想提出一个观点。
34赞 alestanis 10/30/2012
@ragatskynet 这取决于:计算一定次数或声明和定义新变量会更快吗?在大多数情况下,这是不成熟的(和错误的)优化,除非你的评估成本非常高。.lengthlength
13赞 Lightness Races in Orbit 10/30/2012
@Dr.Dredel:这不是比较,而是评估。 比 更快地评估。嗯,据说。0array.length
24赞 Haris Krajina 10/30/2012
值得一提的是,我们谈论的是Ruby,Python等解释型语言。编译语言(例如 Java)在编译器级别进行了优化,这将“平滑”这些差异,以至于是否在声明中并不重要。.lengthfor loop
1赞 Rorchackh 10/30/2012 #9

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

评论

3赞 harper 10/30/2012
+1 其他人没有投反对票吗?尽管对 .length 的重复计算比实际的 inc/decrement 更成问题,但对循环结束的检查可能很重要。我的 C 编译器在循环上给出了一些警告,例如:“备注 #1544-D:(ULP 13.1) 检测到循环计数。建议循环倒计时,因为检测零更容易”
12赞 Akbar Bayati 10/30/2012 #10

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

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

评论

1赞 dcow 10/31/2012
如果 OP 问为什么以不同的顺序遍历相同的矩阵会花费不同的时间就好了。
1赞 Akbar Bayati 10/31/2012
考虑到在操作系统中,其内存管理基于分页,当进程需要不在缓存页面中的数据时,操作系统中会发生页面错误,并且它必须将目标页面带到 CPU 缓存并替换为另一个页面,因此,会导致处理开销,比目标页面在 CPU 缓存中花费更多时间。假设我们定义了一个大型数组,其中的每一行都大于页面操作系统大小,并且我们按行顺序访问它,在这种情况下,页面错误率增加,结果比访问该数组的列顺序慢。
0赞 Peter Cordes 8/21/2015
页面错误与缓存未命中不是一回事。只有当内存被分页到磁盘时,才会出现分页错误。由于缓存局部性(在获取缓存时使用缓存行的所有字节),而不是因为页面错误,因此按多维数组在内存中的存储顺序访问多维数组的速度更快。(除非您的工作集对于您正在使用的计算机来说太大。
35赞 sainiuc 10/30/2012 #11

i--i++

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

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

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

评论

7赞 Pavel P 10/31/2012
你在这里显然错了。现在循环控制需要额外的内部变量(i 及以上),并且根据 JavaScript 引擎,这可能会限制优化代码的潜力。JIT 会将简单的循环转换为直接的机器操作码,但如果循环有太多变量,那么 JIT 将无法进行优化。通常,它受 JIT 使用的体系结构或 CPU 寄存器的限制。初始化一次,然后简单地使用最干净的解决方案,这就是为什么到处推荐它的原因。
0赞 H.-Dirk Schmitt 10/31/2012
附加变量将由通用解释器/编译器的优化器消除。关键是“与 0 比较”——请参阅我的答案以获得进一步的解释。
2赞 thdoan 1/15/2015
最好将“up”放在循环中:for (var i=0, up=Things.length; i<up; i++) {}
0赞 Eve 11/29/2021
为什么内部变量会产生问题?这是错误的,因为循环控件不会有“额外变量”,因为完全一样不是通过引用传递的(就像对象或数组一样),而是直接通过值传递。换句话说,它完全以数字 10 或 100000 的形式插入到循环控件中。您只是在循环之外重命名了它,所以没有任何区别。因此,答案本身是完全有效的。当看到 i < up 循环控件时,看到 i < 10 而不是 i <(对数字 10 的引用)。实际上在里面存储了一个原语,别忘了。upThings.lengthup
22赞 Dr BDO Adams 10/30/2012 #12

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

a++ < length

实际上被编译为

a++
test (a-length)

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

132赞 Barney Szabolcs 10/30/2012 #13

我试着用这个答案给出一个大致的画面。

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

[[就 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);
});

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

[备注:不仅是浏览器,而且你也有一个空间来轻松优化,只需重新定义功能(依赖于浏览器),以便它使用最新的最佳技巧! :) @A.M.K.说,在特殊情况下,它值得使用或。如果你这样做,把它放在窗帘后面。最大的矫枉过正是添加选项来选择循环算法。forEacharray.poparray.shiftforEach

此外,对于低级语言,如果可能的话,最佳实践是使用一些智能库函数进行复杂的循环操作。

这些库还可以将东西(多线程)放在您身后,并且专门的程序员还可以使它们保持最新状态。

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

另一方面,在这种 5e9 情况下展开,它使我从 12 秒下降到 2.5 秒时的 10 秒,以及 2.1 秒时的 20 秒。它没有优化,而优化已经将事情缩短到无法衡量的时间。:)(展开可以按照我上面的方式完成,也可以使用 ,但这并不能使 JavaScript 领先。i++

总而言之:保持/和/对工作面试的差异,坚持或其他复杂的库功能(如果可用)。;)i--i++++ii++array.forEach

评论

18赞 jalf 10/30/2012
关键词是“可以”。展开的循环也可能比原始循环慢。优化时,请始终进行测量,以便准确了解更改产生了什么影响。
1赞 Barney Szabolcs 10/30/2012
@jalf确实如此,+1。不同的解环长度(>=1)在效率上是不同的。这就是为什么如果可能的话,将这项工作留给图书馆会更方便,更不用说浏览器运行在不同的架构上,所以如果他们决定如何做可能会更好。我不认为他们会尝试解开普通的 for 循环。array.each(...)
1赞 A.M.K 10/31/2012
@BarnabasSzabolcs 这个问题是专门针对 JavaScript 的,而不是 C 或其他语言。在JS中是有区别的,请看我下面的答案。虽然不适用于问题,+1个好答案!
1赞 Rohit Jain 11/25/2012
你去吧 - 给你一个徽章。真的很棒的答案。:)+1Great Answer
1赞 Barney Szabolcs 11/27/2012
APL/A++ 也不是一种语言。人们用这些来表达他们对语言细节不感兴趣,而是对这些语言共享的东西感兴趣。
6赞 Arvind Kanjariya 10/30/2012 #14

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

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

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

评论

0赞 Buksy 10/31/2012
多个纳秒循环可以变成几秒钟......当你有时间的时候,优化从来都不是一个坏主意
15赞 BBog 10/30/2012 #15

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

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

例如

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

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

从我所处的位置来看,您问题中表单的主要改进是它没有声明额外的变量(在我的示例中)len

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

评论

1赞 kojiro 10/31/2012
我必须对这里的计算这个词持例外态度。请参阅我对帕维尔回答的评论。ECMA 规范指出,当您引用数组长度时,不会计算它。
0赞 BBog 10/31/2012
“评估”会是更好的选择吗?顺便说一句,有趣的事实,我不知道
0赞 H.-Dirk Schmitt 10/31/2012
附加变量将由通用解释器/编译器的优化器消除。这同样适用于 array.length 计算。关键是“与 0 比较”——请参阅我的答案以获得进一步的解释。
0赞 kojiro 10/31/2012
@H.-DirkSchmitt 撇开你的常识性答案不谈,在 JavaScript 的历史中,编译器并没有优化查找链的性能成本。AFAIK V8 是第一个尝试的。
0赞 BBog 10/31/2012
@H.-DirkSchmitt 就像小次郎说的那样,这是众所周知且公认的伎俩。即使它在现代浏览器中不再相关,这仍然不会使它过时。此外,通过引入新的长度变量或使用 OP 问题中的技巧来做到这一点,这仍然是最好的方法,imo。这只是一件聪明的事情和良好的做法,我认为这与编译器经过优化以处理 JS 中经常以不良方式完成的事情这一事实无关
3赞 the swine 10/30/2012 #16

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

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

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

评论

0赞 Peter Cordes 8/21/2015
作为记录,是 8051 (z80) ISA 中的指令。x86 有 而不是 ,显然 arm 也有类似的东西。我很确定这不是 Javascript 差异的原因;这是因为在循环条件下有更多的 eval 要执行。DJNZdec/jnzinc/cmp/jne
7赞 Dmitry Isaev 10/30/2012 #17

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

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

P.S.:不比 .i--i++

10赞 Javier 10/30/2012 #18

我上一次为此烦恼是在编写 6502 汇编时(8 位,是的!最大的收获是,大多数算术运算(尤其是递减运算)更新了一组标志,其中之一是“达到零”指标。Z

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

评论

0赞 Pavel P 10/31/2012
对于 JavaScript,显然它不适用(因为它在没有此类操作码的 CPU 上运行)。最有可能的是,vs 背后的真正原因是,对于前者,您不会在循环的作用域中引入额外的控制变量。请看下面我的回答......i--i++
1赞 Javier 10/31/2012
是的,它真的不适用;但这是一种非常常见的 C 风格,对于我们这些习惯了它的人来说,它看起来确实更干净。:-)
0赞 Peter Cordes 8/21/2015
在这方面,x86 和 ARM 都类似于 6502。 而不是 x86 案例。您不会看到空循环运行得更快(两者都会使分支吞吐量饱和),但倒计时会略微减少循环开销。当前的英特尔硬件预取器也不会受到降序内存访问模式的困扰。我认为较旧的 CPU 可以跟踪 4 个向后流和 6 或 10 个前向流,IIRC。dec/jnzinc/cmp/jne
25赞 dreamcrash 10/30/2012 #19

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

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

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

编辑:

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

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

Benchmark example.

评论

0赞 johny why 2/11/2021
@dreamcash 在 Chrome 88.x 上,所有反向循环总是比所有正向循环更快。jsben.ch/eng8bmeasurethat.net/Benchmarks/ShowResult/162677jsbench.me/5ykkzsysa9。有时是反向的,有时是反向优化的,有时是反向的。
0赞 dreamcrash 2/11/2021
@johnywhy okey 我错过了对 okey 的理解,所以它仍然有点不同。你会发布这些结果的答案吗?
4赞 Ant 10/31/2012 #20

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

我同意其他人的说法,这些天没有区别。

评论

0赞 johny why 2/11/2021
基准无处不在:jsben.ch:更快,jsben.ch/RpG0K。jsbench.me:更快,jsbench.me/i2kkzuk4kl/1。measurethat.net:更快,measurethat.net/Benchmarks/ShowResult/162675--ii----i
15赞 Pavel P 10/31/2012 #21

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

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

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

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

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

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

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

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

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

评论

3赞 kojiro 10/31/2012
关于如何评估,可能值得仔细措辞。引用它时不会计算长度。(它只是在创建或更改数组索引时设置的属性)。当有额外的成本时,这是因为 JS 引擎没有优化该名称的查找链。array.length
0赞 Pavel P 10/31/2012
好吧,不确定 Ecma 规范说了什么,但了解不同 JavaScript 引擎的一些内部结构并不简单,因为涉及一些内务管理。但是,如果你试着逆向思考:编写数组实现将是相当巧妙的,其中长度需要实际计算:)getLength(){return m_length; }
0赞 kojiro 10/31/2012
ECMA 规范要求已经计算了 length 属性。每当添加或更改属性即数组索引时,都必须立即更新。length
0赞 Pavel P 10/31/2012
我想说的是,如果你试着想一想,就很难违反规范。
1赞 Peter Cordes 8/21/2015
在这方面,x86 就像 ARM。 与。。dec/jnzinc eax / cmp eax, edx / jne
7赞 Salvador Dali 10/31/2012 #22

简而言之:在 JavaScript 中这样做绝对没有区别。

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

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

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

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

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

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

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

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

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

评论

0赞 10/31/2012
有趣的是,但对于在 win7 上的 firefox 16.0.2 下运行的测试,递减循环慢了 29%......编辑:忽略它。反复测试证明没有结论,在随后的运行中,我的测试结果中出现了令人惊讶的噪音。不太清楚为什么。
0赞 10/31/2012
是的,我试图通过关闭其他所有内容并仅运行测试来解决这个问题。仍然得到不稳定的结果。很奇怪。
0赞 Pavel P 10/31/2012
我认为你错过了为什么在 JavaScript 中归零被认为更好的真正要点。这主要是因为这种方式只有一个变量控制循环执行,例如这种方式优化器/JITer 有更大的改进空间。使用不一定会产生性能损失,仅仅是因为 JS 虚拟机足够聪明,可以确定数组是否没有被循环的主体修改。请参阅下面的回答。array.length
1赞 Javier 11/22/2012
吹毛求疵:这个古老的事实(汇编语言优化)并没有过时,只是晦涩难懂。就像你不需要知道一样,除非你真的知道。
4赞 Kunwar Siddharth Singh 10/31/2012 #23

有时,对我们编写代码的方式进行一些非常小的更改可能会对代码的实际运行速度产生重大影响。一个微小的代码更改可以对执行时间产生重大影响的一个领域是,我们有一个处理数组的 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--) {...

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

3赞 Omar Freewan 10/31/2012 #24

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

for 循环的运行方式如下:

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

所以

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

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

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

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

评论

0赞 Dmitry 10/31/2012
var i = Things.length - 1; i >= 0; i--也会计算长度 1 次。
1赞 Olathe 8/21/2013
我不确定“操作将做 2 的补码”是什么意思,但我认为这意味着它会否定某些东西。不,它不会否定任何架构上的任何内容。减去 1 就像加 1 一样简单,你只需做一个借用而不是携带的电路。--
15赞 H.-Dirk Schmitt 10/31/2012 #25

简答

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

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

适用于所有高级语言,并且大多独立于 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 寄存器的变量。这可能会在代码中调用额外的寄存器交换,并且需要在语句中使用更昂贵的 compare 语句if

只有我的 5 美分

对于高级语言来说,可读性(便于维护)作为次要的性能改进更为重要。

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

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

后记

正如在评论中提出的那样:和的区别在于递减之前或之后的评估。--ii--i

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

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

评论

1赞 Afshin Mehrabani 10/31/2012
1+很好的解释。只是一个有点超出范围的问题,你能解释一下 和 之间的区别吗?--ii--
3赞 Dmitry 10/31/2012 #26

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

以下代码花费的时间大不相同。

快:

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 引擎)很慢。最好的方法是使用“<”而不是“<=”(或“=”)和“--i”而不是“i--”。

3赞 Ravi_Parmar 10/31/2012 #27

用非常简单的话来说

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

但在这种情况下,当您进行增量操作时..处理器在变量递增 1 时计算 .length,如果递减。特别是在这种情况下,它只会计算一次 .length,直到我们得到 0。

5赞 Salman A 10/31/2012 #28

++与。 没关系,因为 JavaScript 是一种解释型语言,而不是编译语言。每条指令都会翻译成多种机器语言,您不应该关心血腥的细节。--

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

您应该编写可读的代码。

13赞 A.M.K 10/31/2012 #29

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

所以,给你:

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

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

--i在Firefox中是一样的,而在Chrome中更快。i--i--

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

在 Chrome 和 Firefox 中,缓存的速度明显快,而 Chrome 领先约 170,000 次/秒。arr.length

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

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

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

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

评论

1赞 Pebbl 11/3/2012
据我所知,您的测试似乎只有这么快,因为它将大部分循环的数组大小减小到 0。然而,为了确保事情是公平的,我设计了这个 jsperf 以与每次测试相同的方式创建数组 - 这似乎显示为我的几个浏览器的赢家 - 而不是我所期望的:)jsperf.com/compare-different-types-of-loopingpop.shift()
0赞 jaggedsoft 12/5/2016
投了赞成票,成为唯一一个提到:D的人++i
7赞 Spyryto 1/22/2015 #30

我在jsbench上做了一个比较

正如 alestani 所指出的,在升序循环中需要时间的一件事是评估每次迭代的数组大小。在此循环中:

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

每次递增时都会进行评估。在这一个中:.lengthi

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

您只评估一次,当您声明 .在这一个中:.lengthi

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、回流焊等)
  2. 您可以选择更好的算法
  3. 您可以选择更好的数据结构

但请记住:

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

评论

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