js大量数据计算性能优化

测试相关内容

  • 浏览器版本:Chrome 89.0+
  • ECMAScript: ES5+
  • 测试结果:多次测试随机取值
  • 数据量:100万或1000万

–备注–
由于是做大量数据计算的性能优化,基本不考虑百万级数据以下的计算。
本次测试是为了检验项目运行性能而做,并不具备严谨的实验分析性质,测试结果只能大致作为编码参考。

1. forEach和for循环

在100万条数据遍历的情况下,forEachfor的纯遍历效率大概相差810倍;如果数据量再翻十倍,他们的效率差距可以拉开到1620倍

测试代码

/**
 * forEach和for循环的效率
 */
const random = (min, max) => Math.floor(Math.random() * (max - min)) + min;   
		 
const list = Array.from(Array(1000000), (v,k) => ({num:random(0, 100)}));

console.time("forEach");
list.forEach(el=>{}) 
console.timeEnd("forEach");

console.time("for");
for(let i = 0, len = list.length; i < len; i++){}
console.timeEnd("for");

测试结果

100万条数据

forEach: 8.929931640625 ms
for: 1.205810546875 ms

1000万条数据

forEach: 73.71484375 ms
for: 4.824951171875 ms

测试结论

在大量数据遍历的情况下,请选择for循环

2. indexOf的效率问题

indexOf很常用,特别是在数据位置判断时,但是它也特别耗性能,我们该如何权衡使用它呢?

测试代码

/**
 * 统计100万个0~100的随机值各出现了多少次
 */
const random = (min, max) => Math.floor(Math.random() * (max - min)) + min;

const list = Array.from(Array(10000000), (v, k) => ({ num: random(0, 100) }));

console.time('indexOf');
let arr1 = [];
let count1 = [];
for (let i = 0, len = list.length; i < len; i++) {
  const el = list[i];
  let index = arr1.indexOf(el.num);
  if (index === -1) {
    arr1.push(el.num);
    count1.push(1);
    index = arr1.indexOf(el.num);
  }
  count1[index] = count1[index] + 1;
}
console.timeEnd('indexOf');

console.time('Map');
let arr2 = new Map();
for (let i = 0, len = list.length; i < len; i++) {
  const el = list[i];
  const num = arr2.get(el.num) ? arr2.get(el.num) : 0;
  arr2.set(el.num, num + 1);
}
console.timeEnd('Map');

console.time('object');
let arr3 = {};
for (let i = 0, len = list.length; i < len; i++) {
  const el = list[i];
  const num = arr3[el.num] ? arr3[el.num] : 0;
  arr3[el.num] = num + 1;
}
console.timeEnd('object');

测试结果

100万条数据

indexOf: 67.65283203125 ms
Map: 32.763916015625 ms
object: 6.612060546875 ms

1000万条数据

indexOf: 652.962890625 ms
Map: 291.893798828125 ms
object: 107.72802734375 ms

测试结论

indexOf 的效率是真的不太行,在大量数据计算中,我们应该减少对它的使用。Map的效率比indexOf高出不少,网上也有使用 Map 重写 Array.phototype.indexOf 的方案,感兴趣的可以自己试试。

3. 遍历时的浅拷贝

遍历时怎么进行浅拷贝?

测试代码

/**
 * 遍历时浅拷贝以及属性修改
 */
  const list = Array.from(Array(10000000), (v, k) => ({ a: 1, b: 1, c: 1 }));

  console.time('Spread');
  let arr1 = [];
  for (let i = 0, len = list.length; i < len; i++) {
    let obj = { ...list[i] };
    obj.a = 2;
    arr1.push(obj);
  }
  console.timeEnd('Spread');
    
  console.time('assign');;
  let arr2 = [];
  for (let i = 0, len = list.length; i < len; i++) {
    let obj = Object.assign({a:2}, list[i]);
    arr2.push(obj);
  }
  console.timeEnd('assign');
  
  console.time('obj');;
  let arr3 = [];
  for (let i = 0, len = list.length; i < len; i++) {
    let obj = {a: 2, b: list[i].b, c: list[i].c };
    arr3.push(obj);
  }
  console.timeEnd('obj');

测试结果

100万条数据

Spread: 98.81201171875 ms
assign: 177.4150390625 ms
obj: 54.517822265625 ms

1000万条数据

Spread: 1115.959716796875 ms
assign: 1040.654052734375 ms
obj: 546.125244140625 ms

测试结论

需要进行浅拷贝修改属性时,如果代码量不大,请选择另外创建一个新对象进行属性赋值。

4. filter和for的性能比较1

filter 非常好用,一行代码就解决了过滤。但是在大量数据需要过滤的情况下,它的效率又如何呢?

测试代码


/**
 * 研究filter和for的效率问题
 * 统计 a,b,c三个值大于50出现的次数
 */
const random = (min, max) => Math.floor(Math.random() * (max - min)) + min;   

const list = Array.from(Array(1000000), (v,k) => ({a: random(0, 1000), b: random(0, 1000), c: random(0, 1000), d: random(0, 1000), e: random(0, 1000)}));

console.time("filter");
let a = 0;
a = list.filter(el=> el.a > 50).length;
console.timeEnd("filter");
  
console.time("for");
let a1 = 0;
for(let i = 0, len = list.length; i < len; i++){
  list[i].a > 50 && (a1 = a1 + 1 );
}
console.timeEnd("for");

测试结果

100万条数据

filter: 24.39306640625 ms
for: 4.648193359375 ms

1000万条数据

filter: 215.532958984375 ms
for: 33.93994140625 ms

测试结论
for 循环虽然老旧,但正因为其老旧,所以它终究是大爷。在数据量小的情况下,建议还是使用 filter,毕竟代码简洁可维护性更佳。在大量数据的情况下,还是使用 for 吧。

5. filter和for的性能比较2

多条属性过滤时,哪种写法效率更高?

测试代码

  /**
   * 研究filter和for的效率问题
   * 统计 a,b,c三个值大于50出现的次数
   */
  const random = (min, max) => Math.floor(Math.random() * (max - min)) + min;   

  const list = Array.from(Array(1000000), (v,k) => ({a: random(0, 1000), b: random(0, 1000), c: random(0, 1000), d: random(0, 1000), e: random(0, 1000)}));

  let obj = {a: 0, b: 0, c: 0}
  
  console.time("filter");
  for(let key in obj){
    obj[key] = list.filter(el=> el[key] > 50).length;
  }
  console.timeEnd("filter");
  
  
  console.time("for in");
  for(let key in obj){
    for(let i = 0, len = list.length; i < len; i++){
      list[i][key] > 50 && (obj[key] = obj[key] + 1 )  ;
    }
  }
  console.timeEnd("for in");
  
  console.time("for");
  let a = 0;
  let b = 0;
  let c = 0;
  for(let i = 0, len = list.length; i < len; i++){
    list[i]['a'] > 50 && (a = a + 1 );
    list[i]['b'] > 50 && (b = b + 1 );
    list[i]['c'] > 50 && (c = c + 1 );
  }
  console.timeEnd("for");

测试结果

100万条数据

filter: 111.63623046875 ms
for in: 65.217041015625 ms
for: 8.674072265625 ms

1000万条数据

filter: 812.73486328125 ms
for in: 668.552978515625 ms
for: 62.194091796875 ms

测试结论
for 循环虽然老旧,但正因为其老旧,所以它终究是大爷。越是朴实无华的写法,越能节省更多的性能。

6. 数据去重问题1 (数组去重)

使用 new Set 以及 for循环去重时,相同代码,1万数据和一百万数据去重的效率一致吗?

测试代码

  /**
   * 研究数组去重的效率问题
   */
  const random = (min, max) => Math.floor(Math.random() * (max - min)) + min;   
  
  const list = Array.from(Array(1000000), (v,k) => random(0, 100000 ));
  
  console.time("Set");
  let arr = Array.from(new Set(list));
  console.timeEnd("Set");

  
  console.time("for");
  let obj = {};
  let data = [];
  for(let i = 0, len = list.length; i < len; i++){
    const item = list[i];
    if( obj[item] === undefined ){ 
        obj[item] = item;
        data.push(item);
    }
  }
  console.timeEnd("for");

测试结果

1万条数据

Set: 0.490234375 ms
for: 4.195068359375 ms

100万条数据

Set: 31.82421875 ms
for: 15.007080078125 ms

1000万条数据

Set: 283.175048828125 ms
for: 76.7900390625 ms

测试结论
在数据量较小时,new Set的效率占据绝对优势,然鹅随着数据量的增大,for 循环利用对象属性的唯一性去重方案貌似效率更稳定。其实不止是 new Set 存在这个问题,其他的 ES6+ 语法例如 Mapfilter 等都存在这个问题。这不是说我们应该少用 ES6 语法,而是说我们在使用js进行大量数据计算操作时,应该考虑到它们的性能边界问题。在我们的常规项目中,其实很少会出现需要使用js进行大量的计算这种场景。

7. 数据去重问题2 (数组对象去重)

使用 reduce 以及 for循环去重时,相同代码,1万数据和一百万数据去重的效率一致吗?

测试代码

 
/**
 * 研究数组对象去重的效率问题
 * 对 数组对象属性a进行去重
 */
const random = (min, max) => Math.floor(Math.random() * (max - min)) + min;   

const list = Array.from(Array(10000), (v,k) => ({a: random(0, 10000)}));

console.time("reduce");
let obj = {};
const data = list.reduce((accumulator, current) => {
  if( obj[current.a] === undefined ){ 
      obj[current.a] = current.a;
      accumulator.push(current);
  }
  return accumulator;
}, []);
console.timeEnd("reduce");


console.time("for");
let obj1 = {};
let data1 = [];
for(let i = 0, len = list.length; i < len; i++){
  const item = list[i];
  if( obj1[item.a] === undefined ){ 
      obj1[item.a] = item.a;
      data1.push(item);
  }
}
console.timeEnd("for");

测试结果

1万条数据

reduce: 1.341064453125 ms
for: 2.615234375 ms

100万条数据

reduce: 16.10498046875 ms
for: 9.541015625 ms

1000万条数据

reduce: 132.93310546875 ms
for: 72.7529296875 ms

测试结论
结论大致和上面那一个相同,reduce 的效率也会随着数据量的增大与 for 循环的效率差距拉大。

未完待续。。。

  • 15
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值