提问人:djdd87 提问时间:8/27/2009 最后编辑:dreamcrashdjdd87 更新时间:2/9/2021 访问量:144887
循环反向真的更快吗?
Are loops really faster in reverse?
问:
我听说过很多次。倒数时 JavaScript 循环真的更快吗?如果是这样,为什么?我看过一些测试套件示例,表明反向循环更快,但我找不到任何解释!
我假设这是因为循环不再需要在每次检查属性时都计算属性以查看它是否完成,而只是根据最终数值进行检查。
J.F.公司
for (var i = count - 1; i >= 0; i--)
{
// count is only evaluated once and then the comparison is always on 0.
}
答:
你现在做这件事的方式并不快(除了它是一个无限循环,我猜你是想做的.i--
如果你想让它更快,请执行以下操作:
for (i = 10; i--;) {
//super fast loop
}
当然,在这么小的循环中,你不会注意到它。它更快的原因是因为你在检查它是否为“真”时递减了 i(当它达到 0 时,它的计算结果为“false”)
评论
(i = 10; i--;)
这可以通过 JavaScript(和所有语言)最终被转换为在 CPU 上运行的操作码来解释。CPU 总是有一条指令用于与零进行比较,这真是太快了。
顺便说一句,如果你能保证总是,你可以简化为:count
>= 0
for (var i = count; i--;)
{
// whatever
}
评论
回答这类问题的最佳方法是实际尝试一下。设置一个循环,计算一百万次迭代或其他方式,并且双向执行。对两个循环进行计时,并比较结果。
答案可能取决于您使用的浏览器。有些结果会与其他结果不同。
评论
这家伙比较了很多 javascript 中的循环,在很多浏览器中。他还有一个测试套件,所以你可以自己运行它们。
在所有情况下(除非我在阅读中错过了一个),最快的循环是:
var i = arr.length; //or 10
while(i--)
{
//...
}
评论
--i
var current = arr[i-1];
喜欢它,很多标记,但没有答案:D
简单地说,与零的比较总是最快的比较
所以 (a==0) 实际上比 (a==5) 更快地返回 True
它很小,微不足道,一个集合中有 1 亿行,它是可以衡量的。
即在循环中,您可能会说 i <= array.length 并递增 i
在向下循环中,您可能会说 i >= 0 的位置,然后递减 i。
比较更快。不是循环的“方向”。
评论
在许多情况下,这基本上与处理器可以比其他比较更快地与零进行比较这一事实无关。
这是因为只有少数 Javascript 引擎(JIT 列表中的引擎)真正生成机器语言代码。
大多数 Javascript 引擎都会构建源代码的内部表示,然后对其进行解释(要了解这是什么样的,请查看 Firefox 的 SpiderMonkey 页面底部附近的内容)。通常,如果一段代码几乎做同样的事情,但导致更简单的内部表示,它会运行得更快。
请记住,对于简单的任务,例如从变量中添加/减去一个变量,或将变量与某个变量进行比较,解释器从一个内部“指令”移动到下一个“指令”的开销非常高,因此 JS 引擎内部使用的“指令”越少越好。
帮助他人避免头痛---对此投赞成票!!
这个页面上最流行的答案不适用于 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;
}
评论
while (i--)
它不是比 .实际上,它们的速度都一样快。i--
i++
在升序循环中需要花费的时间是评估每个数组的大小。在此循环中:i
for(var i = array.length; i--;)
当您声明 时,您只计算一次,而对于此循环.length
i
for(var i = 1; i <= array.length; i++)
每次递增时都会进行评估,当检查是否 ..length
i
i <= array.length
在大多数情况下,您甚至不应该担心这种优化。
评论
.length
length
0
array.length
.length
for loop
这只是一个猜测,但也许是因为处理器更容易将某些内容与 0 ( i >= 0 ) 进行比较,而不是与另一个值 ( i < Things.length) 进行比较。
评论
这取决于阵列在内存中的位置以及访问该阵列时内存页的命中率。
在某些情况下,由于命中率的增加,按列顺序访问数组成员比按行顺序访问数组成员更快。
评论
i--
和i++
下面的代码和你的一样快,但使用了一个额外的变量:
var up = Things.length;
for (var i = 0; i < up; i++) {
Things[i]
};
建议不要每次都评估数组的大小。对于大型阵列,可以看到性能下降。
评论
for (var i=0, up=Things.length; i<up; i++) {}
up
Things.length
up
它不是 or ,而是 compare 操作。与你可以使用与 0 比较,而与你需要将其与长度进行比较。在处理器上,与零比较通常是可用的,而与有限整数比较需要减法。--
++
--
++
a++ < length
实际上被编译为
a++
test (a-length)
因此,编译时在处理器上需要更长的时间。
我试着用这个答案给出一个大致的画面。
括号中的以下想法是我的信念,直到我最近测试了这个问题:
[[就 C/C++ 等低级语言而言,编译代码时,处理器在变量为零(或非零)时具有特殊的条件跳转命令。
另外,如果你关心这么多的优化,你可以去代替,因为是一个单处理器命令,而意味着。++i
i++
++i
i++
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.说,在特殊情况下,它值得使用或。如果你这样做,把它放在窗帘后面。最大的矫枉过正是添加选项来选择循环算法。forEach
array.pop
array.shift
forEach
此外,对于低级语言,如果可能的话,最佳实践是使用一些智能库函数进行复杂的循环操作。
这些库还可以将东西(多线程)放在您身后,并且专门的程序员还可以使它们保持最新状态。
我做了更多的审查,结果发现在 C/C++ 中,
即使对于 5e9 = (50,000x100,000) 操作,如果测试是针对 @alestanis 所说的常量进行的,则上升和下降之间没有区别。(JsPerf 结果有时不一致,但大体上是一样的:你不能有很大的不同。
所以恰好是一件相当“时髦”的事情。它只会让你看起来像一个更好的程序员。:)--i
另一方面,在这种 5e9 情况下展开,它使我从 12 秒下降到 2.5 秒时的 10 秒,以及 2.1 秒时的 20 秒。它没有优化,而优化已经将事情缩短到无法衡量的时间。:)(展开可以按照我上面的方式完成,也可以使用 ,但这并不能使 JavaScript 领先。i++
总而言之:保持/和/对工作面试的差异,坚持或其他复杂的库功能(如果可用)。;)i--
i++
++i
i++
array.forEach
评论
array.each(...)
+1
Great Answer
这不依赖于 or 符号,但取决于您在循环中应用的条件。--
++
例如:如果变量具有静态值,则循环速度比循环每次检查条件(如数组的长度或其他条件)更快。
但不要担心这种优化,因为这次它的效果是以纳秒为单位的。
评论
我在 Sublime Text 2 中看到了同样的建议。
就像已经说过的,主要的改进不是在 for 循环中的每次迭代中评估数组的长度。这是一种众所周知的优化技术,在 JavaScript 中特别有效,当数组是 HTML 文档的一部分时(对所有元素执行 a 操作)。for
li
例如
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--
评论
好吧,我不了解 JavaScript,它实际上应该只是重新计算数组长度的问题,并且可能与关联数组有关(如果您只是递减,则不太可能需要分配新条目 - 如果数组是密集的,也就是说,有人可能会为此进行优化)。
在低级汇编中,有一个循环指令,称为 DJNZ(如果为非零,则递减和跳转)。因此,递减和跳转都在一条指令中,这使得它可能比 INC 和 JL / JB 稍微快一点(递增,如果小于则跳/低于则跳)。此外,与零进行比较比与另一个数字进行比较更简单。但所有这些都是微不足道的,还取决于目标架构(可能会有所不同,例如在智能手机中的 Arm 上)。
我不希望这种低级差异对口译语言产生如此大的影响,我只是没有在回复中看到 DJNZ,所以我想我会分享一个有趣的想法。
评论
DJNZ
dec/jnz
inc/cmp/jne
for(var i = array.length; i--; )
并不快多少。但是当你替换为 时,它可能会快得多(因为它在每次迭代中都会被调用)。这就是区别。array.length
super_puper_function()
如果你打算在2014年改变它,你不需要考虑优化。如果你打算用“搜索和替换”来改变它,你不需要考虑优化。如果你没有时间,你不需要考虑优化。但现在,你有时间考虑一下。
P.S.:不比 .i--
i++
我上一次为此烦恼是在编写 6502 汇编时(8 位,是的!最大的收获是,大多数算术运算(尤其是递减运算)更新了一组标志,其中之一是“达到零”指标。Z
所以,在循环结束时,你只做了两个指令:(递减)和(如果不是零,则跳),不需要比较!DEC
JNZ
评论
i--
i++
dec/jnz
inc/cmp/jne
既然你对这个主题感兴趣,请看一下 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 中进行测试,我得到了以下循环:
评论
曾经有人说--i更快(在C++中),因为只有一个结果,即递减值。i-- 需要将递减后的值存储回 i,并保留原始值作为结果 (j = i--;)。在大多数编译器中,这会占用两个寄存器而不是一个寄存器,这可能导致另一个变量必须写入内存,而不是保留为寄存器变量。
我同意其他人的说法,这些天没有区别。
评论
--i
i--
--i
我认为说这比在 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 i
i--
评论
array.length
getLength(){return m_length; }
length
dec/jnz
inc eax / cmp eax, edx / jne
简而言之:在 JavaScript 中这样做绝对没有区别。
首先,您可以自己测试一下:
您不仅可以在任何 JavaScript 库中测试和运行任何脚本,还可以访问以前编写的一大堆脚本,以及查看不同平台上不同浏览器中执行时间之间的差异的能力。
因此,据您所见,在任何环境中的性能都没有区别。
如果要提高脚本的性能,可以尝试执行以下操作:
- 有一个语句,这样你就不会在循环中每次都计算它的值
var a = array.length;
- 执行循环展开 http://en.wikipedia.org/wiki/Loop_unwinding
但你必须明白,你能获得的改进将是微不足道的,大多数情况下你甚至不应该关心它。
我个人的看法为什么会出现这样的误解(Dec vs Inc)
很久很久以前,有一个通用的机器指令,DSZ(递减和跳过零)。用汇编语言编程的人使用此指令来实现循环以保存寄存器。现在这个古老的事实已经过时了,我很确定你不会使用这种伪改进在任何语言中获得任何性能改进。
我认为,在我们这个时代,这种知识传播的唯一方式就是阅读别人的代码。看到这样的结构并询问为什么实现它,这里的答案是:“它提高了性能,因为它与零相比”。你对同事的更高知识感到困惑,并认为利用它来变得更聪明:-)
评论
array.length
有时,对我们编写代码的方式进行一些非常小的更改可能会对代码的实际运行速度产生重大影响。一个微小的代码更改可以对执行时间产生重大影响的一个领域是,我们有一个处理数组的 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--) {...
此代码在开始时仍然只计算一次数组的大小,但不是将循环计数器与变量进行比较,而是将其与常量进行比较。由于常量比变量更易于访问,并且由于我们的赋值语句比以前少了一个,因此我们的第三个版本代码现在比第二个版本效率略高,并且比第一个版本效率高得多。
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 次。
--
简答
对于普通代码,尤其是在像 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 美分
对于高级语言来说,可读性(便于维护)作为次要的性能改进更为重要。
通常,从数组开始到结束的经典迭代会更好。
从数组结束到开始的快速迭代会导致可能不需要的反向序列。
后记
正如在评论中提出的那样:和的区别在于递减之前或之后的评估。--i
i--
i
最好的解释是尝试一下;-)下面是一个 Bash 示例。
% i=10; echo "$((--i)) --> $i"
9 --> 9
% i=10; echo "$((i--)) --> $i"
10 --> 9
评论
--i
i--
首先,在任何编程语言(包括 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--”。
用非常简单的话来说
“i--和我++。实际上,它们都需要相同的时间”。
但在这种情况下,当您进行增量操作时..处理器在变量递增 1 时计算 .length,如果递减。特别是在这种情况下,它只会计算一次 .length,直到我们得到 0。
++
与。 没关系,因为 JavaScript 是一种解释型语言,而不是编译语言。每条指令都会翻译成多种机器语言,您不应该关心血腥的细节。--
那些谈论使用(或)来有效利用汇编指令的人是错误的。这些指令适用于整数算术,JavaScript 中没有整数,只有数字。--
++
您应该编写可读的代码。
由于其他答案似乎都没有回答您的具体问题(其中一半以上显示了 C 示例并讨论了低级语言,您的问题是针对 JavaScript的),我决定编写自己的答案。
所以,给你:
简单的答案:通常更快,因为它不必在每次运行时都运行与 0 的比较,各种方法的测试结果如下:i--
测试结果:正如这个 jsPerf 所“证明”的那样,实际上是迄今为止最快的循环。但是,专注于 , ,正如您在问题中提出的,以下是 jsPerf(它们来自多个 jsPerf,请参阅下面的来源)结果总结:arr.pop()
--i
i--
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 更快,在任何浏览器中都不会相反。++i
i++
简短的总结:是迄今为止最快的循环;对于特别提到的循环,是最快的循环。arr.pop()
i--
资料来源:http://jsperf.com/fastest-array-loops-in-javascript/15、http://jsperf.com/ipp-vs-ppi-2
我希望这能回答你的问题。
评论
pop
.shift()
++i
正如 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];
}
如果你能在不牺牲易读性的情况下内联你的代码,而不是调用一个函数,你就可以有一个更快一个数量级的循环!
注意:由于浏览器越来越擅长内联简单的函数,这实际上取决于你的代码有多复杂。所以,在优化之前先进行剖析,因为
- 瓶颈可能在其他地方(ajax、回流焊等)
- 您可以选择更好的算法
- 您可以选择更好的数据结构
但请记住:
代码是为人们阅读而编写的,只是顺便为机器执行而编写的。
评论
for