for 循环性能探究与优化

632 阅读2分钟

第一个循环

在我们开始学习编程的时候就接触过for循环

var arr = new Array(10).fill(1)

for (var i = 0 ; i < arr.length ; i++ ) {
  console.log(arr[i])
}

这是很简单的一段,记得当初在用java或者C++的时候说过为了提高访问效率需要把数组长度缓存到一个变量中

var arr = new Array(10).fill(1)
var len = arr.length

for (var i = 0 ; i < len ; i++ ) {
  console.log(arr[i])
}

那么在我们前端的浏览器、Node里面也是这样吗?

确定for循环写法

因为ES6中新加了let声明变量的方式,所以有些方式需要增加以下对比;同时使用数组的索引访问增加真实使用场景时间消耗。

var arr = Array.from(new Array(999999)).map((_, i) => i)

// 不缓存数组长度 var
for (var i1 = 0; i1 < arr.length; i1++) {(arr[i1])}

// 缓存数组长度 var
for (var i2 = 0, len2 = arr.length; i2 < len2; i2++) {(arr[i2])}

// 不缓存数组长度 let
for (let i3 = 0; i3 < arr.length; i3++) {(arr[i3])}

// 缓存数组长度 let
for (let i4 = 0, len4 = arr.length; i4 < len4; i4++) {(arr[i4])}

// 数组倒序 var
for (var i5 = arr.length - 1; i5 >= 0; i5--) {(arr[i5])}

// 数组倒序 let
for (let i6 = arr.length - 1; i6 >= 0; i6--) {(arr[i6])}

// 真值判断 var
for (var i6 = 0, item; item = arr[i6]; i6++) {(arr[i6])}

// 真值判断 let
for (let i6 = 0, item; item = arr[i6]; i6++) {(arr[i6])}

// forEach
a.forEach(function(item7) {(item7)

// map
a.map(function(item7) {(item7)

// for of
for(item8 of a) {(item8)}

// while var
var i = 0
while(i < arr.length) {
  (arr[i++])
}

// while let
let i = 0
while(i < arr.length) {
  (arr[i++])
}

// while let 缓存数组长度
let i = 0, len = arr.length
while(i < len) {
  (arr[i++])
}

// for in
for(i8 in a) {(arr[i8])}

浏览器中运行时间

时间判断:使用console.timeconsole.timeEnd记录每种循环方式的整体执行时间。

内存消耗判断:使用process.memoryUsage()比对执行前后内存中已用到的堆的差值。当然要先判断process是否存在 typeof process !== 'undefined'才能使用

局部作用域

# Chrome
局部作用域 不缓存数组长度 var: 3.096923828125 ms
局部作用域 缓存数组长度 var: 2.745849609375 ms
局部作用域 不缓存数组长度 let: 3.07373046875 ms
局部作用域 缓存数组长度 let: 5.197998046875 ms
局部作用域 数组倒序 var: 2.751953125 ms
局部作用域 数组倒序 let: 2.7138671875 ms
局部作用域 真值判断 var: 0.038818359375 ms
局部作用域 真值判断 let: 0.034912109375 ms
局部作用域 forEach: 15.8369140625 ms
局部作用域 map: 30.466064453125 ms
局部作用域 for of: 62.18017578125 ms
局部作用域 while var: 3.120849609375 ms
局部作用域 while let: 3.925048828125 ms
局部作用域 while let 缓存数组长度: 2.635009765625 ms
局部作用域 for in: 367.190185546875 ms

# Firefox
局部作用域 不缓存数组长度 var285 毫秒
局部作用域 缓存数组长度 var129 毫秒
局部作用域 不缓存数组长度 let249 毫秒
局部作用域 缓存数组长度 let136 毫秒
局部作用域 数组倒序 var135 毫秒
局部作用域 数组倒序 let136 毫秒
局部作用域 真值判断 var1 毫秒
局部作用域 真值判断 let0 毫秒
局部作用域 forEach:4 毫秒
局部作用域 map:20 毫秒
局部作用域 for of:251 毫秒
局部作用域 while var235 毫秒
局部作用域 while let238 毫秒
局部作用域 while let 缓存数组长度:141 毫秒
局部作用域 for in1556 毫秒

全局作用域

# Chrome
不缓存数组长度 var: 17.60791015625 ms
缓存数组长度 var: 13.378173828125 ms
不缓存数组长度 let: 18.7080078125 ms
缓存数组长度 let: 21.89697265625 ms
数组倒序 var: 26.673095703125 ms
数组倒序 let: 13.211181640625 ms
真值判断 var: 0.013671875 ms
真值判断 let: 0.012939453125 ms
forEach: 25.43505859375 ms
map: 41.006103515625 ms
for of: 38.648193359375 ms
while var: 11.234130859375 ms
while let: 15.410888671875 ms
while let 缓存数组长度: 13.287841796875 ms
for in: 394.052978515625 ms

# Firefox
不缓存数组长度 var928 毫秒
缓存数组长度 var948 毫秒
不缓存数组长度 let428 毫秒
缓存数组长度 let250 毫秒
数组倒序 var907 毫秒
数组倒序 let192 毫秒
真值判断 var0 毫秒
真值判断 let1 毫秒
forEach:4 毫秒
map:20 毫秒
for of:362 毫秒
while var812 毫秒
while let733 毫秒
while let 缓存数组长度:714 毫秒
for in1429 毫秒
  • 在全局作用域中
    • let 整体好于 var
  • 局部作用域中
    • 用时整体比全局作用域短
    • let 和 var无明显区别(性能同全局环境中let差不多);
    • Firefox中不缓存数组长度虽然好于在全局环境,但是 let 还是快 var 很多
    • Firefox中缓存数组还是有点效果的

总结:全局环境中var需要在全局作用域中查找,let是块作用域中查找,所以性能好;但是在方法的局部作用域中无明显差别。(问题是变量查找不是应该按照作用域链向上查找吗,性能应该差不多吧)

在node中运行时间

局部作用域 不缓存数组长度 var: 3.777ms
局部作用域 缓存数组长度 var: 2.765ms
局部作用域 不缓存数组长度 let: 4.521ms
局部作用域 缓存数组长度 let: 6.082ms
局部作用域 数组倒序 var: 3.672ms
局部作用域 数组倒序 let: 6.448ms
局部作用域 真值判断 var: 0.181ms
局部作用域 真值判断 let: 0.148ms
局部作用域 forEach: 27.809ms
局部作用域 map: 37.32ms
局部作用域 for of: 35.697ms
局部作用域 while var: 3.953ms
局部作用域 while let: 2.643ms
局部作用域 while let 缓存数组长度: 2.58ms
局部作用域 for in: 458.263ms

不缓存数组长度 var: 10.03ms
缓存数组长度 var: 17.274ms
不缓存数组长度 let: 15.581ms
缓存数组长度 let: 17.644ms
数组倒序 var: 14.843ms
数组倒序 let: 14.887ms
真值判断 var: 0.016ms
真值判断 let: 0.02ms
forEach: 45.607ms
map: 54.476ms
for of: 57.174ms
while var: 9.187ms
while let: 13.486ms
while let 缓存数组长度: 15.922ms
for in: 640.726ms
  • 是否在全局变量,只对使用for循环的方法有影响(局部变量的时候会快点)
  • 是否使用let还是var,对运行时间没有太大影响,使用let还会慢一点。

总结

对于for循环的优化

  • 是否缓存数组长度没有太大的影响,因为各个编译器对这一块可能做了优化
  • 使用let还是var在浏览器(let 全局作用域的时候会快一点)和Node(无影响)表现不一,
  • 局部作用域中整体比全局作用域好
  • 只有一个方法是表现最好的真值判断法,各个端都很理想

因此在数据量相对小的地方可以使用for循环,当较大是可以使用真值判断法,当有复杂需求时可能还要回到for循环

数组操作性能探究

// TODO

局部环境代码

/* eslint-disable */

var a = Array.from(new Array(999999)).map((_, i) => i)

function loop(key, fun) {
  console.time(key)
  // if (typeof process !== 'undefined') {
  //   const startHeap = process.memoryUsage().heapUsed
  //   fun()
  //   const endHeap = process.memoryUsage().heapUsed
  //   const heapDiff = endHeap - startHeap
  //   console.timeEnd(key)
  //   console.log('\t已用到的堆的差值: ', heapDiff)
  // } else {
    fun()
    console.timeEnd(key)
  // }
}

loop('局部作用域 不缓存数组长度 var', _ => {
  for (var i1 = 0; i1 < a.length; i1++) {(a[i1])}
})

loop('局部作用域 缓存数组长度 var', _ => {
  for (var i2 = 0, len2 = a.length; i2 < len2; i2++) {(a[i2])}
})

loop('局部作用域 不缓存数组长度 let', _ => {
  for (let i3 = 0; i3 < a.length; i3++) {(a[i3])}
})

loop('局部作用域 缓存数组长度 let', _ => {
  for (let i4 = 0, len4 = a.length; i4 < len4; i4++) {(a[i4])}
})

loop('局部作用域 数组倒序 var', _ => {
  for (var i5 = a.length - 1; i5 >= 0; i5--) {(a[i5])}
})

loop('局部作用域 数组倒序 let', _ => {
  for (let i6 = a.length - 1; i6 >= 0; i6--) {(a[i6])}
})

loop('局部作用域 真值判断 var', _ => {
  for (var i6 = 0, item; item = a[i6]; i6++) {(a[i6])}
})

loop('局部作用域 真值判断 let', _ => {
  for (let i6 = 0, item; item = a[i6]; i6++) {(a[i6])}
})

loop('局部作用域 forEach', _ => {
  a.forEach(function(item7) {(item7)})
})

loop('局部作用域 map', _ => {
  a.map(function(item7) {(item7)})
})

loop('局部作用域 for of', _ => {
  for(item8 of a) {(item8)}
})

loop('局部作用域 while var', _ => {
  var i = 0
  while(i < a.length) { (a[i++]) }
})

loop('局部作用域 while let', _ => {
  let i = 0
  while(i < a.length) { (a[i++]) }
})

loop('局部作用域 while let 缓存数组长度', _ => {
  let i = 0, len = a.length
  while(i < len) { (a[i++]) }
})

// var a1 = Array.from(new Array(99999)).map((_, i) => i)

loop('局部作用域 for in', _ => {
  for(i8 in a) {(a[i8])}
})

全局环境代码

/* eslint-disable */

var a = Array.from(new Array(999999)).map((_, i) => i)

console.time('不缓存数组长度 var')
for (var i1 = 0; i1 < a.length; i1++) {(a[i1])}
console.timeEnd('不缓存数组长度 var')

console.time('缓存数组长度 var')
for (var i2 = 0, len2 = a.length; i2 < len2; i2++) {(a[i2])}
console.timeEnd('缓存数组长度 var')

console.time('不缓存数组长度 let')
for (let i3 = 0; i3 < a.length; i3++) {(a[i3])}
console.timeEnd('不缓存数组长度 let')

console.time('缓存数组长度 let')
for (let i4 = 0, len4 = a.length; i4 < len4; i4++) {(a[i4])}
console.timeEnd('缓存数组长度 let')

console.time('数组倒序 var')
for (var i5 = a.length - 1; i5 >= 0; i5--) {(a[i5])}
console.timeEnd('数组倒序 var')

console.time('数组倒序 let')
for (let i6 = a.length - 1; i6 >= 0; i6--) {(a[i6])}
console.timeEnd('数组倒序 let')

console.time('真值判断 var')
for (var i6 = 0, item; item = a[i6]; i6++) {(a[i6])}
console.timeEnd('真值判断 var')

console.time('真值判断 let')
for (let i6 = 0, item; item = a[i6]; i6++) {(a[i6])}
console.timeEnd('真值判断 let')

console.time('forEach')
a.forEach(function(item7) {(item7)})
console.timeEnd('forEach')

console.time('map')
a.map(function(item7) {(item7)})
console.timeEnd('map')

console.time('for of')
for(item8 of a) {(item8)}
console.timeEnd('for of')

console.time('while var')
var i9 = 0
while(i9 < a.length) {(a[i9++])}
console.timeEnd('while var')

console.time('while let')
let i10 = 0
while(i10 < a.length) {(a[i10++])}
console.timeEnd('while let')

console.time('while let 缓存数组长度')
let i11 = 0, len = a.length
while(i11 < len) {(a[i11++])}
console.timeEnd('while let 缓存数组长度')

// var a1 = Array.from(new Array(99999)).map((_, i) => i)

console.time('for in')
for(i8 in a) {(a[i8])}
console.timeEnd('for in')

参考

juejin.cn/post/684490… juejin.cn/post/684490… www.jb51.net/article/808…