shhider / shhider.github.io

我的博客。前端内容正在迁移到 Issues 🔝
http://shhider.github.io
1 stars 0 forks source link

[性能优化] 试试用 Set 代替 Object 做缓存 #9

Open shhider opened 4 years ago

shhider commented 4 years ago

背景

最近又开始做表格的性能优化,优化过几波后,现在只能是抠一些细节。由于表格的单元格数量、公式数量常常是几十上百万,所以细节的改变也算是能带来不错的反馈。

这两天盯上的是各种缓存,目前基本上都是通过 Object 做的,key => value。其中不少场景的数据本身没有 key,还得写专门生成 key 的方法。没错,现在就是准备用Set代替 Object 来做数据的缓存。

下面就通过几个测试来看看Set用作缓存有没有比Object更快。

TL; DR;

对于 Key 是必需的场景,则可以考虑用 Map 代替。这可能就是下一篇文章了。

追加:经测试,几个环境下,除了遍历元素,Map 的性能都不如Object

Set 的基本信息

Set对象能保存一系列数据的唯一值。对于 Object 元素,是直接判断的引用是否一致。另外,Set 提供了 add, delete, clear, forEach 等方法用于操作元素。

其它Set的具体细节请查阅 MDN:Set - JavaScript | MDN

下面开始测试性能吧~

测试环境

// 生成测试数据
const dataToCache = [];
for (let i = 0; i < 1000; i++) {
  dataToCache.push({
    id: genGuid(),
    balabala: "balabala"
  });
}

添加缓存项

// 添加缓存数据
objCache = {};
run1wTimes("cache by Object - add item", () => {
  for (let i = 0; i < dataToCache.length; i++) {
    const data = dataToCache[i];
    objCache[data.id] = data;
  }
});

setCache = new Set();
run1wTimes("cache by Set - add item", () => {
  for (let i = 0; i < dataToCache.length; i++) {
    const data = dataToCache[i];
    setCache.add(data);
  }
});

多次执行结果如下:

# node
cache by Object - add item: 266.779ms
cache by Set - add item: 181.644ms

cache by Object - add item: 273.227ms
cache by Set - add item: 177.329ms

cache by Object - add item: 255.003ms
cache by Set - add item: 181.770ms

cache by Object - add item: 241.004ms
cache by Set - add item: 176.350ms

cache by Object - add item: 259.306ms
cache by Set - add item: 182.561ms

# chrome
cache by Object - add item: 233.2041015625ms
cache by Set - add item: 208.697021484375ms

cache by Object - add item: 242.2080078125ms
cache by Set - add item: 193.538330078125ms

cache by Object - add item: 228.8369140625ms
cache by Set - add item: 193.158935546875ms

cache by Object - add item: 241.336181640625ms
cache by Set - add item: 194.253173828125ms

cache by Object - add item: 226.299072265625ms
cache by Set - add item: 191.123046875ms

cache by Object - add item: 226.415771484375ms
cache by Set - add item: 196.301025390625ms

# Safari
cache by Object - add item: 628.628ms
cache by Set - add item: 429.687ms

cache by Object - add item: 671.169ms
cache by Set - add item: 438.944ms

cache by Object - add item: 625.141ms
cache by Set - add item: 421.651ms

cache by Object - add item: 620.798ms
cache by Set - add item: 434.040ms

cache by Object - add item: 630.158ms
cache by Set - add item: 431.311ms

# Firefox
# 耗时有点异常,但多次执行依然是这样
cache by Object - add item: 4624ms
cache by Set - add item: 3722ms

cache by Object - add item: 4557ms
cache by Set - add item: 3604ms

cache by Object - add item: 4612ms
cache by Set - add item: 3405ms

cache by Object - add item: 4510ms
cache by Set - add item: 4424ms

cache by Object - add item: 4659ms
cache by Set - add item: 3383ms

各测试环境下,Set 的添加项操作都比 Object 快,各幅度(Object 耗时 - Set 耗时) / Object 耗时分别是:

遍历缓存项

let objCache = {};
let setCache = new Set();
let temp;

for (let i = 0; i < dataToCache.length; i++) {
  const data = dataToCache[i];
  objCache[data.id] = data;
}

for (let i = 0; i < dataToCache.length; i++) {
  const data = dataToCache[i];
  setCache.add(data);
}

run1wTimes("cache by Object - forEach", () => {
  for (const key in objCache) {
    if (objCache.hasOwnProperty(key)) {
      temp = objCache[key];
    }
  }
});

run1wTimes("cache by Set - forEach", () => {
  setCache.forEach(item => {
    temp = item;
  });
});

多次执行结果:

# node
cache by Object - forEach: 1283.685ms
cache by Set - forEach: 143.221ms

cache by Object - forEach: 1321.415ms
cache by Set - forEach: 141.727ms

cache by Object - forEach: 1295.686ms
cache by Set - forEach: 140.379ms

cache by Object - forEach: 1318.022ms
cache by Set - forEach: 140.797ms

cache by Object - forEach: 1312.564ms
cache by Set - forEach: 143.722ms

# chrome
cache by Object - forEach: 1312.255859375ms
cache by Set - forEach: 141.73095703125ms

cache by Object - forEach: 1298.447021484375ms
cache by Set - forEach: 136.48193359375ms

cache by Object - forEach: 1326.23974609375ms
cache by Set - forEach: 139.885986328125ms

cache by Object - forEach: 1317.888916015625ms
cache by Set - forEach: 138.151123046875ms

cache by Object - forEach: 1387.4541015625ms
cache by Set - forEach: 136.711181640625ms

# Safari
cache by Object - forEach: 555.257ms
cache by Set - forEach: 312.070ms

cache by Object - forEach: 613.752ms
cache by Set - forEach: 291.776ms

cache by Object - forEach: 578.782ms
cache by Set - forEach: 294.459ms

cache by Object - forEach: 654.297ms
cache by Set - forEach: 321.344ms

cache by Object - forEach: 611.440ms
cache by Set - forEach: 308.352ms

# Firefox
cache by Object - forEach: 3362ms
cache by Set - forEach: 965ms

cache by Object - forEach: 3306ms
cache by Set - forEach: 952ms

cache by Object - forEach: 3359ms
cache by Set - forEach: 972ms

cache by Object - forEach: 3248ms
cache by Set - forEach: 1052ms

cache by Object - forEach: 3537ms
cache by Set - forEach: 973ms

各环境下,Set 的添加项都比 Object 快,各幅度(Object 耗时 - Set 耗时) / Object 耗时分别是:

删除缓存项

let objCache = {};
let setCache = new Set();

for (let i = 0; i < dataToCache.length; i++) {
  const data = dataToCache[i];
  objCache[data.id] = data;
}

for (let i = 0; i < dataToCache.length; i++) {
  const data = dataToCache[i];
  setCache.add(data);
}

run1wTimes("cache by Object - delete item", () => {
  const index = Math.floor(Math.random() * dataToCache.length);
  const data = dataToCache[index];
  delete objCache[data.id];
});

run1wTimes("cache by Set - delete item", () => {
  const index = Math.floor(Math.random() * dataToCache.length);
  const data = dataToCache[index];
  setCache.delete(data);
});

多次执行结果:

# node
cache by Object - delete item: 5.210ms
cache by Set - delete item: 2.859ms

cache by Object - delete item: 2.591ms
cache by Set - delete item: 3.795ms

cache by Object - delete item: 2.434ms
cache by Set - delete item: 3.317ms

cache by Object - delete item: 2.535ms
cache by Set - delete item: 3.581ms

cache by Object - delete item: 3.331ms
cache by Set - delete item: 3.369ms

# chrome
cache by Object - delete item: 1.30517578125ms
cache by Set - delete item: 0.529052734375ms

cache by Object - delete item: 0.638916015625ms
cache by Set - delete item: 0.635986328125ms

cache by Object - delete item: 0.60498046875ms
cache by Set - delete item: 0.470947265625ms

cache by Object - delete item: 0.64404296875ms
cache by Set - delete item: 0.85986328125ms

cache by Object - delete item: 0.60400390625ms
cache by Set - delete item: 0.5068359375ms

# safari
cache by Object - delete item: 4.752ms
cache by Set - delete item: 2.998ms

cache by Object - delete item: 1.089ms
cache by Set - delete item: 0.771ms

cache by Object - delete item: 1.441ms
cache by Set - delete item: 0.866ms

cache by Object - delete item: 1.805ms
cache by Set - delete item: 1.583ms

cache by Object - delete item: 2.620ms
cache by Set - delete item: 1.560ms

# firefox
cache by Object - delete item: 16ms
cache by Set - delete item: 6ms

cache by Object - delete item: 11ms
cache by Set - delete item: 10ms

cache by Object - delete item: 8ms
cache by Set - delete item: 6ms

cache by Object - delete item: 14ms
cache by Set - delete item: 9ms

cache by Object - delete item: 7ms
cache by Set - delete item: 6ms

各测试环境下的删除操作,大多是 Set 更快一些 ,但在 Node 环境中 Object缓存反而更快。各幅度(Object 耗时 - Set 耗时) / Object 耗时分别是:

Done。

结论就是,用作缓存时,增删和遍历操作,Set的性能更好。除了 Node 环境下的删除项操作。

追加:缓存简单数据类型

将测试数据改为简单数据类型(字符串),测试其添加缓存项:

const dataToCache = [];
for (let i = 0; i < 1000; i++) {
  dataToCache.push(genGuid());
}

const objCache = {};
run1wTimes("cache by Object - add item", () => {
  for (let i = 0; i < dataToCache.length; i++) {
    const data = dataToCache[i];
    objCache[data] = data;
  }
});

const setCache = new Set();
run1wTimes("cache by Set - add item", () => {
  for (let i = 0; i < dataToCache.length; i++) {
    setCache.add(dataToCache[i]);
  }
});

多次执行得到:

# node
cache by Object - add item: 234.102ms
cache by Set - add item: 405.451ms

cache by Object - add item: 230.297ms
cache by Set - add item: 367.350ms

cache by Object - add item: 236.763ms
cache by Set - add item: 413.732ms

cache by Object - add item: 221.878ms
cache by Set - add item: 387.975ms

cache by Object - add item: 239.362ms
cache by Set - add item: 380.882ms

# chrome
cache by Object - add item: 216.985107421875ms
cache by Set - add item: 276.950927734375ms

cache by Object - add item: 210.3837890625ms
cache by Set - add item: 270.06103515625ms

cache by Object - add item: 212.114990234375ms
cache by Set - add item: 271.041259765625ms

cache by Object - add item: 209.23828125ms
cache by Set - add item: 272.879150390625ms

cache by Object - add item: 207.015869140625ms
cache by Set - add item: 270.810791015625ms

# safari
cache by Object - add item: 599.806ms
cache by Set - add item: 420.768ms

cache by Object - add item: 588.941ms
cache by Set - add item: 425.122ms

cache by Object - add item: 583.876ms
cache by Set - add item: 419.824ms

cache by Object - add item: 590.346ms
cache by Set - add item: 429.632ms

cache by Object - add item: 579.668ms
cache by Set - add item: 423.003ms

# firefox
cache by Object - add item: 3470ms
cache by Set - add item: 3266ms

cache by Object - add item: 3598ms
cache by Set - add item: 3307ms

cache by Object - add item: 3534ms
cache by Set - add item: 3307ms

cache by Object - add item: 3503ms
cache by Set - add item: 3259ms

cache by Object - add item: 3651ms
cache by Set - add item: 3520ms

可以看到,在不同的环境下,性能结果出现了相反的情况。V8 引擎下,Object 更快;Safari 中Set依然有明显优势;Firefox 中相差不大。具体幅度是:

Others

完整测试代码

const dataToCache = [];
for (let i = 0; i < 1000; i++) {
  dataToCache.push({
    id: genGuid(),
    balabala: "balabala"
  });
}

let objCache;
let setCache;
let temp;

const again = () => {
  objCache = {};
  run1wTimes("cache by Object - add item", () => {
    for (let i = 0; i < dataToCache.length; i++) {
      const data = dataToCache[i];
      objCache[data.id] = data;
    }
  });

  setCache = new Set();
  run1wTimes("cache by Set - add item", () => {
    for (let i = 0; i < dataToCache.length; i++) {
      const data = dataToCache[i];
      setCache.add(data);
    }
  });

  // run1wTimes("cache by Object - forEach", () => {
  //   for (const key in objCache) {
  //     if (objCache.hasOwnProperty(key)) {
  //       temp = objCache[key];
  //     }
  //   }
  // });

  // run1wTimes("cache by Set - forEach", () => {
  //   setCache.forEach(item => {
  //     temp = item;
  //   });
  // });

  // run1wTimes("cache by Object - delete item", () => {
  //   const index = Math.floor(Math.random() * dataToCache.length);
  //   const data = dataToCache[index];
  //   delete objCache[data.id];
  // });

  // run1wTimes("cache by Set - delete item", () => {
  //   const index = Math.floor(Math.random() * dataToCache.length);
  //   const data = dataToCache[index];
  //   setCache.delete(data);
  // });
};

again();

function run1wTimes(name,fn, times=10000) {
    console.time(name)
    for(let i=0;i<times;i++){
        fn(i)
    }
    console.timeEnd(name)
}

function genGuid(prefix) {
  let len = 17;
  const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  const results = [];
  while ((len -= 1)) {
    results.push(chars[parseInt(Math.random() * 62)]);
  }
  const guid = results.join("");

  if (prefix) {
    return `${prefix}-${guid}`;
  }
  return guid;
};

可以了解下