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

JavaScript循环性能-为什么将迭代器递减到0比递增快,javascript,performance,loops,Javascript,Performance,Loops,史蒂夫·桑德斯(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

史蒂夫·桑德斯(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-1
i>=0
,所以让我们澄清一下(我也很困惑)

以下是循环语法的一般用法:

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

    首先计算此变量声明

  • 条件-
    i--

    此表达式在每次循环迭代之前求值。它将在第一次通过循环之前递减变量。如果此表达式的计算结果为
    false
    ,则循环结束。在JavaScript中是
    0==false
    ,因此如果
    i
    最终等于
    0
    ,则解释为
    false
    ,循环结束

  • 最终表达

    此表达式在每次循环迭代结束时(在下一次条件求值之前)求值。这里不需要,它是空的。这三个表达式在for循环中都是可选的


for循环语法不是问题的一部分,但因为它有点不常见,我认为澄清它很有趣。也许它之所以更快,是因为它使用较少的表达式(<代码> 0=false < /COD> >技巧”。

< P>我已经对C++和C++(类似语法)进行了一个基准测试。实际上,与
do while
while
相比,
for
循环的性能在本质上是不同的。在C++中,性能在递增时更大。它也可能取决于编译器

我认为,在Javascript中,这一切都取决于浏览器(Javascript引擎),但这种行为是意料之中的。Javascript为使用DOM进行了优化。因此,假设您在每次迭代中循环得到一组DOM元素,并在必须删除它们时增加一个计数器。先删除
0
元素,然后删除
1
元素,然后跳过取代
0
的元素。当向后循环时,问题消失了。我知道给出的示例并不完全正确,但我确实遇到过必须从不断变化的对象集合中删除项的情况


因为向后循环比向前循环更不可避免,所以我猜测JS引擎正是为此而优化的。

我相信原因是您将循环终点与0进行比较,这比再次比较
(或另一个JS变量)更快

这是因为序数运算符
=
是多态的,因此这些运算符需要在运算符的左右两侧进行类型检查,以确定应该使用什么样的比较行为

这里有一些非常好的基准:


我不确定它是否更快,但我看到的一个原因是,当您使用增量迭代大型元素数组时,您倾向于编写:

for(var i = 0; i < array.length; i++) {
 ...
}
for(变量i=0;i
您实际上是在访问数组的length属性N(元素数)次。 而当你递减时,你只能访问它一次。这可能是一个原因

但您也可以按如下方式编写递增循环:

for(var i = 0, len = array.length; i < len; i++) {
 ...
}
for(变量i=0,len=array.length;i
你自己计时了吗?桑德斯先生对现代口译员的看法可能是错误的。这正是一个好的编译器编写人员可以发挥巨大作用的那种优化。

可以很容易地说,一次迭代可以有更少的指令。让我们比较一下这两个:

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

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

for(var i=0;i我对Javascript不太清楚,在现代编译器中这可能无关紧要,但在“过去”这段代码:

for (i = 0; i < n; i++){
  .. body..
}
而反向计数代码

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

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

因此,在循环内部,它只执行两个额外指令,而不是四个指令。

使用反向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]);
}
在我看来,这段代码至少比(i=length;i--;)的
可读性更强。
这段代码的“性能”更高。 因为for循环中的每个参数都是可选的,所以您甚至可以跳过第一个参数

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

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

这样,您甚至可以跳过对
[初始表达式]的检查
。因此,最后只剩下一个操作。

我也一直在探索循环速度,并有兴趣发现递减比递增快的小贴士。但是,我还没有找到一个测试来证明这一点。在jsperf上有很多循环基准。下面是一个测试递减的例子:

不过,缓存数组长度(也是Steve Souders推荐的书)似乎是一个成功的优化。

对于2017年的增量vs.减量
在现代JS引擎中,循环的
增量通常比递减快(基于personal Benchmark.JS测试),也更传统:

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

最新的V8版本(Chrome,Node)对
array.length
进行了优化,因此
length=array.length
在任何情况下都可以有效地省略。

在现代JS引擎中,正向循环和反向循环之间的差异几乎不再存在。但是
for (let i = 0, length = array.length; i < length; i++) { ... }