简体   繁体   English

JavaScript 循环性能 - 为什么将迭代器递减到 0 比递增更快

[英]JavaScript loop performance - Why is to decrement the iterator toward 0 faster than incrementing

In his book Even Faster Web Sites Steve Sounders writes that a simple way to improve the performance of a loop is to decrement the iterator toward 0 rather than incrementing toward the total length ( actually the chapter was written by Nicholas C. Zakas ). Steve Sounders 在他的《 甚至更快的网站》一书中写道,提高循环性能的一个简单方法是将迭代器递减到 0,而不是朝着总长度递增(实际上这一章是由 Nicholas C. Zakas 编写的)。 This change can result in savings of up to 50% off the original execution time, depending on the complexity of each iteration.根据每次迭代的复杂性,此更改最多可将原始执行时间节省 50%。 For example:例如:

var values = [1,2,3,4,5];
var length = values.length;

for (var i=length; i--;) {
   process(values[i]);
}

This is nearly identical for the for loop, the do-while loop, and the while loop.这是几乎相同for循环中, do-while循环,并while循环。

I'm wondering, what's the reason for this?我想知道,这是什么原因? Why is to decrement the iterator so much faster?为什么要更快地减少迭代器? (I'm interested in the technical background of this and not in benchmarks proving this claim.) (我对此的技术背景感兴趣,而不是对证明此声明的基准感兴趣。)


EDIT: At first sight the loop syntax used here looks wrong.编辑:乍一看,这里使用的循环语法看起来是错误的。 There is no length-1 or i>=0 , so let's clarify (I was confused too).没有length-1i>=0 ,所以让我们澄清一下(我也很困惑)。

Here is the general for loop syntax:这是一般的 for 循环语法:

for ([initial-expression]; [condition]; [final-expression])
   statement
  • initial-expression - var i=length初始表达式- var i=length

    This variable declaration is evaluated first.首先评估此变量声明。

  • condition - i--条件- i--

    This expression is evaluated before each loop iteration.该表达式在每次循环迭代之前计算。 It will decrement the variable before the first pass through the loop.它将在第一次通过循环之前递减变量。 If this expression evaluates to false the loop ends.如果此表达式的计算结果为false则循环结束。 In JavaScript is 0 == false so if i finally equals 0 it is interpreted as false and the loop ends.在 JavaScript 中是0 == false所以如果i最终等于0它被解释为false并且循环结束。

  • final-expression最终表达式

    This expression is evaluated at the end of each loop iteration (before the next evaluation of condition ).该表达式在每次循环迭代结束时进行计算(在下一次计算condition 之前)。 It's not needed here and is empty.这里不需要它,它是空的。 All three expressions are optional in a for loop.所有三个表达式在 for 循环中都是可选的。

The for loop syntax is not part of the question, but because it's a little bit uncommon I think it's interesting to clarify it. for 循环语法不是问题的一部分,但因为它有点不常见,我认为澄清它很有趣。 And maybe one reason it's faster is, because it uses less expressions (the 0 == false "trick").也许它更快的一个原因是,因为它使用较少的表达式( 0 == false “技巧”)。

I'm not sure about Javascript, and under modern compilers it probably doesn't matter, but in the "olden days" this code:我不确定 Javascript,在现代编译器下它可能无关紧要,但在“过去”这段代码:

for (i = 0; i < n; i++){
  .. body..
}

would generate会产生

move register, 0
L1:
compare register, n
jump-if-greater-or-equal L2
-- body ..
increment register
jump L1
L2:

while the backward-counting code而向后计数的代码

for (i = n; --i>=0;){
  .. body ..
}

would generate会产生

move register, n
L1:
decrement-and-jump-if-negative register, L2
.. body ..
jump L1
L2:

so inside the loop it's only doing two extra instructions instead of four.所以在循环内部它只执行两个额外的指令而不是四个。

I believe the reason is because you're comparing the loop end point against 0, which is faster then comparing again < length (or another JS variable).我相信原因是因为您将循环终点与 0 进行比较,这比再次比较< length (或另一个 JS 变量)要快。

It is because the ordinal operators <, <=, >, >= are polymorphic, so these operators require type checks on both left and right sides of the operator to determine what comparison behaviour should be used.正是因为序数运算符<, <=, >, >=是多态的,所以这些运算符需要在运算符的左右两侧进行类型检查,以确定应该使用什么比较行为。

There's some very good benchmarks available here:这里有一些非常好的基准测试:

What's the Fastest Way to Code a Loop in JavaScript 在 JavaScript 中编写循环的最快方法是什么

It is easy to say that an iteration can have less instructions.很容易说一次迭代可以有更少的指令。 Let's just compare these two:让我们来比较一下这两个:

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

for (var i=length; i--;) {
}

When you count each variable access and each operator as one instruction, the former for loop uses 5 instructions (read i , read length , evaluate i<length , test (i<length) == true , increment i ) while the latter uses just 3 instructions (read i , test i == true , decrement i ).当您将每个变量访问和每个运算符算作一条指令时,前者for循环使用 5 条指令(读取i 、读取length 、评估i<length 、 test (i<length) == true 、 increment i ),而后者只使用3 条指令(读取i ,测试i == true ,递减i )。 That is a ratio of 5:3.那是5:3的比例。

What about using a reverse while loop then:那么使用反向while循环怎么样:

var values = [1,2,3,4,5]; 
var i = values.length; 

/* i is 1st evaluated and then decremented, when i is 1 the code inside the loop 
   is then processed for the last time with i = 0. */
while(i--)
{
   //1st time in here i is (length - 1) so it's ok!
   process(values[i]);
}

IMO this one at least is a more readble code than for(i=length; i--;) IMO 这至少是一个比for(i=length; i--;)更易读的代码

for increment vs. decrement in 2017 for 2017年的增量与减量

In modern JS engines incrementing in for loops is generally faster than decrementing (based on personal Benchmark.js tests), also more conventional:在现代 JS 引擎中forfor循环中递增通常比递减更快(基于个人 Benchmark.js 测试),也更传统:

for (let i = 0; i < array.length; i++) { ... }

It depends on the platform and array length if length = array.length has any considerable positive effect, but usually it doesn't:如果length = array.length有任何相当大的积极影响,则取决于平台和数组长度,但通常不会:

for (let i = 0, length = array.length; i < length; i++) { ... }

Recent V8 versions (Chrome, Node) have optimizations for array.length , so length = array.length can be efficiently omitted there in any case.最近的 V8 版本(Chrome、Node)对array.length进行了优化,因此在任何情况下都可以有效地省略length = array.length

There is an even more "performant" version of this.还有一个更“高性能”的版本。 Since each argument is optional in for loops you can skip even the first one.由于每个参数在 for 循环中都是可选的,因此您甚至可以跳过第一个。

var array = [...];
var i = array.length;

for(;i--;) {
    do_teh_magic();
}

With this you skip even the check on the [initial-expression] .有了这个,您甚至可以跳过对[initial-expression]的检查。 So you end up with just one operation left.所以你最终只剩下一个手术了。

I've been exploring loop speed as well, and was interested to find this tidbit about decrementing being faster than incrementing.我也一直在探索循环速度,并且有兴趣找到这个关于递减比递增更快的花絮。 However, I have yet to find a test that demonstrates this.但是,我还没有找到证明这一点的测试。 There are lots of loop benchmarks on jsperf. jsperf 上有很多循环基准测试。 Here is one that tests decrementing:这是测试递减的一个:

http://jsperf.com/array-length-vs-cached/6 http://jsperf.com/array-length-vs-cached/6

Caching your array length, however (also recommended Steve Souders' book) does seem to be a winning optimization.然而,缓存你的数组长度(也推荐 Steve Souders 的书)似乎是一个成功的优化。

in modern JS engines, the difference between forward and reverse loops is almost non-existent anymore.在现代 JS 引擎中,正向循环和反向循环之间的区别几乎不再存在。 But the performance difference comes down to 2 things:但性能差异归结为两件事:

a) extra lookup every of length property every cycle a) 每个周期额外查找每个长度属性

 //example: for(var i = 0; src.length > i; i++) //vs for(var i = 0, len = src.length; len > i; i++)

this is the biggest performance gain of a reverse loop, and can obviously be applied to forward loops.这是反向循环的最大性能增益,显然可以应用于正向循环。

b) extra variable assignment. b) 额外的变量赋值。

the smaller gain of a reverse loop is that it only requires one variable assignment instead of 2反向循环的较小增益是它只需要一个变量分配而不是 2

 //example: var i = src.length; while(i--)

I've conducted a benchmark on C# and C++ (similar syntax).我已经对 C# 和 C++(类似的语法)进行了基准测试。 There, actually, the performance differs essentially in for loops, as compared to do while or while .实际上,与do whilewhile相比, for循环的性能本质上for不同的。 In C++, performance is greater when incrementing.在 C++ 中,递增时性能更高。 It may also depend on the compiler.它也可能取决于编译器。

In Javascript, I reckon, it all depends on the browser (Javascript engine), but this behavior is to be expected.在 Javascript 中,我认为这完全取决于浏览器(Javascript 引擎),但这种行为是可以预料的。 Javascript is optimized for working with DOM. Javascript 已针对 DOM 进行了优化。 So imagine you loop through a collection of DOM elements you get at each iteration, and you increment a counter, when you have to remove them.因此,想象一下您遍历每次迭代中获得的 DOM 元素集合,并在必须删除它们时增加一个计数器。 You remove the 0 element, then 1 element, but then you skip the one that takes 0 's place.您删除0元素,然后是1元素,但随后您跳过了占据0位置的元素。 When looping backwards that problem disappears.当向后循环时,这个问题就消失了。 I know that the example given isn't just the right one, but I did encounter situations where I had to delete items from an ever-changing object collection.我知道给出的示例并不正确,但我确实遇到过必须从不断变化的对象集合中删除项目的情况。

Because backward looping is more often inevitable than forward looping, I am guessing that the JS engine is optimized just for that.因为后向循环比前向循环更不可避免,我猜测 JS 引擎就是为此而优化的。

I am not sure if it's faster but one reason i see is that when you iterate over an array of large elements using increment you tend to write:我不确定它是否更快,但我看到的一个原因是,当您使用增量迭代大型元素数组时,您倾向于编写:

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

You are essentially accessing the length property of the array N (number of elements) times.您实际上是在访问数组 N(元素数)的长度属性。 Whereas when you decrement, you access it only once.而当您递减时,您只能访问它一次。 That could be a reason.这可能是一个原因。

But you can also write incrementing loop as follows:但是你也可以写如下递增循环:

for(var i = 0, len = array.length; i < len; i++) {
 ...
}

Have you timed it yourself?你自己计时了吗? Mr. Sounders might be wrong with regards to modern interpreters.关于现代口译员,Sounders 先生可能是错误的。 This is precisely the sort of optimization in which a good compiler writer can make a big difference.这正是优秀的编译器编写者可以发挥重大作用的优化类型。

It's not faster (at least in modern browsers):不是更快(至少在现代浏览器中):

 // Double loops to check the initialization performance too const repeats = 1e3; const length = 1e5; console.time('Forward'); for (let j = 0; j < repeats; j++) { for (let i = 0; i < length; i++) {} } console.timeEnd('Forward'); // 58ms console.time('Backward'); for (let j = repeats; j--;) { for (let i = length; i--;) {} } console.timeEnd('Backward'); // 64ms

The difference is even bigger in case of an array iteration:在数组迭代的情况下,差异甚至更大:

 const repeats = 1e3; const array = [...Array(1e5)]; console.time('Forward'); for (let j = 0; j < repeats; j++) { for (let i = 0; i < array.length; i++) {} } console.timeEnd('Forward'); // 34ms console.time('Backward'); for (let j = 0; j < repeats; j++) { for (let i = array.length; i--;) {} } console.timeEnd('Backward'); // 64ms

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用迭代器的Javascript for循环在i的左边的中间和减量运算符? - Javascript for loop with iterator In the middle and decrement operator to the left of i? 在简单的循环测试中,Javascript比Classical C快100,为什么? - Javascript is 100 faster than Classical C in simple for loop test, why? 为什么递归比JavaScript上的求和函数的for循环更快? - Why is recursion faster than a flat for loop for a summation function on JavaScript? 在JavaScript中,为什么“反向”循环比“for”快一个数量级? - In JavaScript, why is a “reverse while” loop an order of magnitude faster than “for”? 在 javascript 迭代器中递增计数器 - Incrementing counter in a javascript iterator 为什么JavaScript不用于循环递增? - Why isn't JavaScript for loop incrementing? for / in循环递减javascript - for/in loop decrement javascript 有没有比 for 循环更快的方法来对 javascript 中的图像进行阈值处理? - Is there a faster way than a for loop for thresholding an image in javascript? 为什么 lodash _.each 比原生 for 循环快? - Why is lodash _.each faster than the native for loop? 为什么 then 回调中的这个循环比异步 function 中的循环更快? - Why is this loop in a then callback faster than in an async function?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM