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

71

在他的书中《Even Faster Web Sites》中,史蒂夫·桑德斯(Steve Sounders)写道,改进循环性能的简单方法是将迭代器向0递减,而不是向总长度递增(实际上这一章是由尼古拉斯C.扎卡斯(Nicholas C. Zakas)撰写的)。这种改变可以使每次迭代的复杂度不同,从而节省高达50%的原始执行时间。例如:

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

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

对于for循环、do-while循环和while循环,这几乎是相同的。

我想知道,这是什么原因?为什么递减迭代器会更快?(我对技术背景感兴趣,而不是证明这种说法的基准测试。)


编辑:乍一看,这里使用的循环语法看起来是错误的。没有length-1i>=0,所以让我们澄清一下(我也感到困惑)。

以下是通用的for循环语法:

for ([initial-expression]; [condition]; [final-expression])
   statement
  • initial-expression - var i=length

    首先会执行这个变量声明。

  • condition - i--

    这个表达式会在每次循环迭代之前被计算。它会在第一次通过循环之前使变量递减。如果这个表达式计算结果为false,则循环结束。在JavaScript中,0 == false,所以如果最终i等于0,那么它会被解释成false,循环也会结束。

  • final-expression

    这个表达式在每次循环迭代的末尾被计算(在下一次计算condition之前)。这里不需要这个表达式,因此为空。在for循环中,这三个表达式都是可选的。

这里并不是问题的重点,但由于它有点不太常见,我认为澄清一下会很有趣。另外,它更快的原因可能是因为使用了较少的表达式(0 == false“技巧”)。


1
你难道没有漏掉终止条件吗? - Dave O.
9
终止条件是 i--。在递减之前,当 i 为0时,它将变为0(假)。由于该条件具有递减变量本身的副作用,因此在语句中不需要第三个(更改/增加/任何其他)表达式。 - cHao
1
@Soundlink:实际上,当 i 等于 0 时,它的值为 false。在其余时间里,它的值为 true。 - cHao
1
@Gumbo:循环第一次检查条件——即读取:它评估i-- - cHao
那么为什么在Chrome中,如果在while循环外访问.length,递减操作会变得非常慢呢?http://jsperf.com/preincrement-vs-postincrement-vs-predecrement-vs-postde/5 - CodeManX
显示剩余4条评论
12个回答

1
你自己计时了吗?Sounders先生可能对于现代解释器是错误的。这正是一个好的编译器编写者可以产生重大影响的优化类型。

不,我自己没有计时。了解当前的JavaScript引擎是否有不同的行为会很有趣。 - Soundlink
@Soundlink:你可能至少有一个网络浏览器(正如你在网站上发布帖子的事实所证明的那样)。试试看吧。 :) - cHao

0

在现代浏览器中,它并不更快:

// 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

在数组迭代的情况下,差异更大:

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接