[英]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-1
或i>=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 引擎中
for
在for
循环中递增通常比递减更快(基于个人 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 while
或while
相比, 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.