循环是否真的更快?
我已经听了好几次了。 向后计数JavaScript循环真的快吗? 如果是这样,为什么? 我见过几个testing套件的例子,显示反向循环更快,但我找不到任何解释为什么!
我假设这是因为循环不再需要评估一个属性,每次检查它是否完成,它只是检查最终的数值。
即
for (var i = count - 1; i >= 0; i--) { // count is only evaluated once and then the comparison is always on 0. }
这不是i++
比i++
更快。 事实上,他们同样快。
上升循环中需要花费的时间是对每个i
计算数组的大小。 在这个循环中:
for(var i = array.length; i--;)
当你声明i
时,你只评估一次.length
,而对于这个循环
for(var i = 1; i <= array.length; i++)
你每增加一次,你检查是否i <= array.length
。
在大多数情况下,您甚至不必担心这种优化 。
这家伙在很多浏览器中比较了很多javascript中的循环。 他还有一个testing套件,所以你可以自己运行它们。
在所有情况下(除非我在阅读中错过了一个)最快的循环是:
var i = arr.length; //or 10 while(i--) { //... }
我试图用这个答案给出一个广泛的图片。
括号内的下列想法是我的信念,直到我刚刚testing了这个问题:
[就像C / C ++这样的低级语言而言,编译代码时,处理器在variables为零(或非零)时有特殊的条件跳转命令。
另外,如果你关心这么多的优化,你可以用++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); }
但其原因可能相当复杂(只是提到,游戏中存在处理器命令预处理和caching处理的问题)。
就高级语言而言 ,就像您所问的JavaScript一样,如果您依赖库,内置函数进行循环,则可以对其进行优化。 让他们决定如何做到最好。
因此,在JavaScript中,我会build议使用类似的东西
array.forEach(function(i) { do_it(i); });
它也不太容易出错,浏览器有机会优化你的代码。
[备注:不仅浏览器,但你也有一个容易优化的空间,只需重新定义forEach
function(依赖于浏览器),以便它使用最新的最好的欺骗! 🙂 @AMK在特殊情况下说,值得使用array.pop
或array.shift
。 如果你这样做,把它放在幕后。 最大的矫枉过正是为forEach
添加选项来select循环algorithm。]
而且,对于低级语言,如果可能的话,最好的做法是对复杂的循环操作使用一些智能库函数。
那些库也可以把东西(multithreading)放在后面,专门的程序员也可以随时更新它们。
我做了一些更仔细的研究,结果发现,在C / C ++中,即使是5e9 =(50,000×100,000)的操作,如果testing是按照像@alestanis所说的那样的常量进行的,那么在上升和下降之间没有区别 。 (JsPerf的结果有时不一致,但基本上都是一样的:你不能做出很大的改变。)
所以--i
恰好是一个“豪华”的东西。 它只会让你看起来像一个更好的程序员。 🙂
另一方面,在这样的情况下,我以10秒的速度把我从12秒降到了2.5秒,到了20秒的时候我降到了2.1秒。 没有优化,优化带来了无法估量的时间。 :)(展开可以按照上面的方式完成,也可以使用i++
,但是这不会在JavaScript中提前实现。)
总而言之:保持i++
/ i++
和++i
/ i++
与求职面试的不同,坚持array.forEach
或其他复杂的库函数。 ;)
i++
和i++
一样快
下面的代码和你的一样快,但是使用一个额外的variables:
var up = Things.length; for (var i = 0; i < up; i++) { Things[i] };
build议不要每次评估数组的大小。 对于大arrays,可以看到性能下降。
由于您对这个主题感兴趣,请查看Greg Reimer关于JavaScript循环基准testing的博客文章,在JavaScript中编写循环的最快方法是什么? :
我为JavaScript编写循环的不同方式构build了一个循环基准testing套件。 已经有一些这些了,但我没有发现任何确认本地数组和HTML集合之间的区别。
您也可以通过打开https://blogs.oracle.com/greimer/resource/loop-test.html
执行循环的性能testing (如果JavaScript在浏览器中被NoScript拦截,则不起作用)。
编辑:
由米兰Adamovsky创build的更新的基准可以在这里运行在不同的浏览器。
对于Mac OS X 10.6中的Firefox 17.0testing,我得到了以下循环:
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; }
这不是--
或++
,它是比较操作。 随着--
你可以使用比较0,而与++
你需要比较它的长度。 在处理器上,与零比较通常是可用的,而与有限整数相比则需要减法。
a++ < length
实际上编译为
a++ test (a-length)
所以在编译时处理器上需要更长的时间。
简短的回答
对于正常的代码,特别是像JavaScript这样的高级语言, i++
和i--
没有性能差异。
性能标准是for
循环和compare语句中的用法。
这适用于所有高级语言 ,并且大多独立于使用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
请注意, end是一个需要CPU寄存器的variables。 这可能会调用代码中的额外的寄存器交换 ,并且在if
语句中需要更昂贵的比较语句。
只是我5美分
对于高级语言来说,便于维护的可读性作为次要的性能改进更为重要。
通常从数组开始到结束的经典迭代更好。
从数组结束到开始更快的迭代会导致可能不需要的反转序列。
发布稿
正如在评论中所提到的那样: – --i
和i--
的区别在于在递减之前或之后对i
的评价。
最好的解释是尝试一下;-)这是一个Bash示例。
% i=10; echo "$((--i)) --> $i" 9 --> 9 % i=10; echo "$((i--)) --> $i" 10 --> 9
我在Sublime Text 2中看到了相同的build议。
就像已经说过的那样,主要的改进是在for循环的每次迭代中不评估数组的长度。 这是一个众所周知的优化技术,当数组是HTML文档的一部分(为所有元素做元素)时,在JavaScript中特别有效。
例如,
for (var i = 0; i < document.getElementsByTagName('li').length; i++)
比…慢得多
for (var i = 0, len = document.getElementsByTagName('li').length; i < len; i++)
从我所站的地方来看,在你的问题中,主要的改进是它没有声明一个额外的variables(在我的例子中是len
)
但是如果你问我,整个问题不是关于i++
vs i--
优化,而是关于在每次迭代时不必评估数组的长度(你可以在jsperf上看到一个基准testing)。
我不认为说i++
在JavaScript中使用i++
速度更快是有道理的。
首先 ,它完全依赖于JavaScript引擎的实现。
其次 ,如果最简单的构造是JIT和翻译成本地指令,那么i++
和i--
将完全依赖执行它的CPU。 也就是说,在ARM(手机)上,下降到0的速度更快,因为递减和比较为零是在单个指令中执行的。
也许,你认为一个是比另一个浪费,因为build议的方式是
for(var i = array.length; i--; )
但build议的方式并不是因为一方比另一方更快,而只是因为如果你写
for(var i = 0; i < array.length; i++)
然后在每次迭代array.length
必须评估(更聪明的JavaScript引擎也许可以找出该循环不会改变数组的长度)。 尽pipe它看起来像一个简单的声明,但它实际上是一些JavaScript引擎在底层调用的函数。
另一个原因,为什么我可以被认为是“更快”是因为JavaScript引擎只需要分配一个内部variables来控制循环(variablesvar i
)。 如果你比较array.length或其他variables,那么必须有多个内部variables来控制循环,而内部variables的数量是JavaScript引擎的有限资源。 循环中使用的variables越less,JIT就有越多的机会进行优化。 这就是为什么我能被认为更快
由于没有其他答案似乎回答你的具体问题(其中一半以上显示C例子和讨论低级语言,你的问题是JavaScript),我决定写我自己的。
所以,在这里你去:
简单的答案: i--
通常比较快,因为每次运行时不需要比较为0,各种方法的testing结果如下:
testing结果:由这个 jsPerf“certificate”, arr.pop()
实际上是迄今为止最快的循环。 但是,关注--i
, i++
, i++
和++i
就像你问的问题一样,下面是jsPerf(它们来自多个jsPerf,请看下面的源代码)结果总结:
--i
和i--
在Firefox中相同, i--
在Chrome中更快。
在Chrome中,基本的for循环( for (var i = 0; i < arr.length; i++)
)比i--
和--i
快,而在Firefox中--i
慢。
在Chrome浏览器和Firefox浏览器中,Chrome浏览器的caching大大提高了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
我希望这回答了你的问题。
这取决于数组在存储器中的位置以及访问该数组时存储器页面的命中率。
在某些情况下,由于命中率的增加,以列顺序访问数组成员比行顺序更快。
我最后一次困扰它的是编写6502汇编(8位,是啊!)。 大多数算术运算(特别是递减)更新了一组标志,其中一个是Z
,即“达到零”指标。
因此,在循环结束时,您只做了两条指令: DEC
(递减)和JNZ
(如果不为零,则跳转),不需要比较!
简而言之: 在JavaScript中这样做绝对没有区别。
首先,你可以自己testing一下:
-
jsperf – 是一个在JavaScript中进行各种性能testing的优秀平台 。
您不仅可以在任何JavaScript库中testing和运行任何脚本,还可以访问大量以前编写的脚本,以及在不同平台上的不同浏览器之间查看执行时间差异的function。
所以就你所看到的,任何环境下的性能都没有区别。
如果你想提高你的脚本的性能,你可以尝试做的事情:
- 有一个
var a = array.length;
语句,这样你就不会每次在循环中计算它的值 - 做循环展开http://en.wikipedia.org/wiki/Loop_unwinding
但是你必须明白,你所能获得的改善将是微不足道的,大多数你甚至不应该在乎。
我自己的意见,为什么这样的误解(十二月与公司)出现
很久很久以前,有一个通用的机器指令DSZ(减量和跳过零)。 以汇编语言编程的人员使用此指令来实现循环以保存寄存器。 现在这个古老的事实已经过时了,而且我敢肯定,在使用这个伪改进的任何语言中,你都不会得到任何性能改进。
我认为这样的知识可以在我们这个时代传播的唯一途径就是当你阅读别人的代码时。 看到这样的结构,问为什么它被执行,这里的答案是:“它提高了性能,因为它比较为零”。 你变得对你的同事更高的知识感到困惑,并认为使用它是更聪明的:-)
这可以通过JavaScript(以及所有语言)来解释,最终被转换为在CPU上运行的操作码。 CPU总是有一个单一的指令比较零,这是该死的快。
另外,如果你能保证count
总是>= 0
,你可以简化为:
for (var i = count; i--;) { // whatever }
这不依赖于--
或++
符号,但取决于您在循环中应用的条件。
例如:如果variables的静态值比每次循环检查条件(如数组的长度或其他条件)更快,则循环更快。
但是不要担心这个优化,因为这个时间的效果是以毫微秒为单位的。
for(var i = array.length; i--; )
并不快得多。 但是当你用super_puper_function()
replacearray.length
时,可能会更快(因为它在每次迭代中被调用)。 这是不同的。
如果你想在2014年改变它,你不需要考虑优化。 如果您要使用“search和replace”来更改它,则无需考虑优化。 如果你没有时间,你不需要考虑优化。 但现在,你有时间思考这个问题了。
PS: i++
不是比i++
更快。
有时候,我们编写代码的方式做了一些很小的改变,可以使我们的代码实际上运行得有多快。 一个小的代码更改可以使执行时间有很大的区别的地方是我们有一个处理数组的循环。 如果数组是网页上的元素(比如单选button),那么这个变化就会产生最大的影响,但是即使数组是Javascript代码的内部数组,也仍然值得应用这个变化。
编写for循环来处理数组的方法如下所示:
for (var i = 0; i < myArray.length; i++) {...
问题在于,使用myArray.length评估数组的长度需要时间,而我们编写循环的方式意味着每次在循环周围都要执行此评估。 如果数组包含1000个元素,那么数组的长度将被评估1001次。 如果我们正在查看单选button并且具有myForm.myButtons.length,则需要更长的时间来进行评估,因为必须首先定位指定表单中的相应button组,之后才能在每次循环中评估长度。
很明显,我们不希望数组的长度在我们处理的时候改变,所有这些长度的重新计算只是增加了处理时间。 (Of course if you have code inside the loop that adds or removes array entries then the array size can change between iterations and so we can't change the code that tests for it)
What we can do to correct this for a loop where the size is fixed is to evaluate the length once at the start of the loop and save it in a variable. We can then test the variable to decide when to terminate the loop. This is much faster than evaluating the array length each time particularly when the array contains more than just a few entries or is part of the web page.
The code to do this is:
for (var i = 0, var j = myArray.length; i < j; i++) {...
So now we only evaluate the size of the array once and test our loop counter against the variable that holds that value each time around the loop. This extra variable can be accessed much faster than evaluating the size of the array and so our code will run much faster than before. We just have one extra variable in our script.
Often it doesn't matter what order we process the array in as long as all of the entries in the array get processed. Where this is the case we can make our code slightly faster by doing away with the extra variable that we just added and processing the array in reverse order.
The final code that processes our array in the most efficient way possible is:
for (var i = myArray.length-1; i > -1; i--) {...
This code still only evaluates the size of the array once at the start but instead of comparing the loop counter with a variable we compare it with a constant. Since a constant is even more effective to access than a variable and since we have one fewer assignment statement than before our third version of the code is now slightly more efficient than the second version and vastly more efficient than the first.
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)
I made a comparison on jsbench .
As alestani pointed out, one thing that takes time in ascending loops, is to evaluate, for each iteration, the size of your array. In this loop:
for ( var i = 1; i <= array.length; i++ )
you evaluate .length
each time you increment i
. In this one:
for ( var i = 1, l = array.length; i <= l; i++ )
you evaluate .length
only once, when you declare i
. In this one:
for ( var i = array.length; i--; )
the comparison is implicit, it happens just before decrementing i
, and the code is very readable. However, what can make a terrific difference, is what you put inside the loop.
Loop with call to function (defined elsewhere):
for (i = values.length; i-- ;) { add( values[i] ); }
Loop with inlined code:
var sum = 0; for ( i = values.length; i-- ;) { sum += values[i]; }
If you can inline your code, instead of calling a function, without sacrificing legibility, you can have a loop an order of magnitude faster!
Note : as browser are becoming good at inlining simple functions, it really depends on how complex your code is. So, profile before optimizing, because
- The bottleneck may be elsewhere (ajax, reflow, …)
- You may choose a better algorithm
- You may choose a better data structure
But remember:
Code is written for people to read, and only incidentally for machines to execute.
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.
Well, I don't know about JavaScript, it should really be just a matter of re-evaluation array length and maybe something to do with the associative arrays (if you only decrement, it is unlikely new entries would need to be allocated – if the array is dense, that is. someone may optimize for that).
In low-level assembly, there is a looping instruction, called DJNZ (decrement and jump if non-zero). So the decrement and jump is all in one instruction, making it possibly ever-so-slightly faster than INC and JL / JB (increment, jump if less than / jump if below). Also, comparing against zero is simpler than comparing against another number. But all that is really marginal and also depends on target architecture (could make difference eg on Arm in a smartphone).
I wouldn't expect this low-level differences to have so great impact on interpreted languages, I just haven't seen DJNZ among the responses so I thought I would share an interesting thought.
It used to be said that –i was faster (in C++) because there is only one result, the decremented value. i– needs to store the decremented value back to i and also retain the original value as the result (j = i–;). In most compilers this used up two registers rather than one which could cause another variable to have to be written to memory rather than retained as a register variable.
I agree with those others that have said it makes no difference these days.
In very simple words
"i– and i++. Actually, they're both takes the same time".
but in this case when you have incremental operation.. processor evaluate the .length every time variable is incremented by 1 and in case of decrement.. particularly in this case, it will evaluate .length only once till we get 0.
++
vs. --
does not matter because JavaScript is an interpreted language, not a compiled language. Each instruction translates to more than one machine language and you should not care about the gory details.
People who are talking about using --
(or ++
) to make efficient use of assembly instructions are wrong. These instruction apply to integer arithmetic and there are no integers in JavaScript, just numbers .
You should write readable code.
First, i++
and i--
take exactly the same time on any programming language, including JavaScript.
The following code take much different time.
Fast:
for (var i = 0, len = Things.length - 1; i <= len; i++) { Things[i] };
Slow:
for (var i = 0; i <= Things.length - 1; i++) { Things[i] };
Therefore the following code take different time too.
Fast:
for (var i = Things.length - 1; i >= 0; i--) { Things[i] };
Slow:
for (var i = 0; i <= Things.length - 1; i++) { Things[i] };
PS Slow is slow only for a few languages (JavaScript engines) because of compiler's optimization. The best way is to use '<' instead '<=' (or '=') and '–i' instead 'i–' .
Not a lot of time is consumed by i– or i++. If you go deep inside the CPU architecture the ++
is more speedy than the --
, since the --
operation will do the 2's complement, but it happend inside the hardware so this will make it speedy and no major difference between the ++
and --
also these operations are considered of the least time consumed in the CPU.
The for loop runs like this:
- Initialize the variable once at the start.
- Check the constraint in the second operand of the loop,
<
,>
,<=
, etc. - Then apply the loop.
- Increment the loop and loop again throw these processes again.
所以,
for (var i = Things.length - 1; i >= 0; i--) { Things[i] };
will calculate the array length only once at the start and this is not a lot of time, but
for(var i = array.length; i--; )
will calculate the length at each loop, so it will consume a lot of time.
The best approach to answering this sort of question is to actually try it. Set up a loop that counts a million iterations or whatever, and do it both ways. Time both loops, and compare the results.
The answer will probably depend on which browser you are using. Some will have different results than others.
Love it, lots of marks up but no answer 😀
Simply put a comparison against zero is always the fastest comparison
So (a==0) is actually quicker at returning True than (a==5)
It's small and insignificant and with 100 million rows in a collection it's measurable.
ie on a loop up you might be saying where i <= array.length and be incrementing i
on a down loop you might be saying where i >= 0 and be decrementing i instead.
The comparison is quicker. Not the 'direction' of the loop.
HELP OTHERS AVOID A HEADACHE — VOTE THIS UP!!!
The most popular answer on this page does not work for Firefox 14 and does not pass the jsLinter. "while" loops need a comparison operator, not an assignment. It does work on chrome, safari, and even ie. But dies in firefox.
THIS IS BROKEN!
var i = arr.length; //or 10 while(i--) { //... }
THIS WILL WORK! (works on firefox, passes the jsLinter)
var i = arr.length; //or 10 while(i>-1) { //... i = i - 1; }
This is just a guess, but maybe it's because it's easier for the processor to compare something with 0 ( i >= 0 ) instead of with another value ( i < Things.length).