JavaScript循环性能-为什么将迭代器递减到0比递增快
史蒂夫·桑德斯(Steve Sounders)在他的书中写道,提高循环性能的一个简单方法是将迭代器减少到0,而不是增加到总长度(实际上这一章是由尼古拉斯·C·扎卡斯(Nicholas C.Zakas)写的)。根据每次迭代的复杂性,此更改可以使原始执行时间减少50%。例如: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
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--
,则循环结束。在JavaScript中是false
,因此如果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++) { ... }