Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
shhider opened this issue Dec 1, 2019 · 0 comments
Open

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

shhider opened this issue Dec 1, 2019 · 0 comments

Comments

@shhider
Copy link
Owner

shhider commented Dec 1, 2019

背景

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

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

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

TL; DR;

  • 缓存引用类型数据时,Set缓存的的添加、遍历、删除项操作都比Object缓存快不少,程度在不同的 JS 引擎下不等;
  • 由于Set是按引用判断某元素是否已存在,因此不适合缓存每次新 new 的引用类型数据;
  • 追加:在 Node 和 Chrome 中(V8),当缓存项是 简单数据类型、做添加缓存项操作时,ObjectSet更快。但就Set方面对比 Safari 和 Firefox,V8 的Set还是最快的,所以也可以认为,V8 的Object的实现太好了。

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

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

Set 的基本信息

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

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

下面开始测试性能吧~

测试环境

  • MacBook Pro 13 (2015 early), 16GB RAM, macOS 10.15.1
  • Chrome 78.0.3904.108
  • Safari 13.0.3
  • Firefox 71.0
  • Node 10.15.1
// 生成测试数据
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 耗时分别是:

  • Node: 30.43%
  • Chrome: 15.76%
  • Safari: 32.08%
  • Firefox: 19.18%

遍历缓存项

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 耗时分别是:

  • Node: 89.13%;
  • Chrome: 89.56%;
  • Safari: 49.16%;
  • Firefox: 75.68%;

删除缓存项

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 耗时分别是:

  • Node: -16.00%
  • Chrome: 12.93%
  • Safari: 31.75%
  • Firefox: 29.31%

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 中相差不大。具体幅度是:

  • Node: -68.28%
  • Chrome: -29.00%
  • Safari: 28%
  • Firefox: 6%

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;
};

可以了解下

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant