Last active
December 9, 2022 08:43
-
-
Save jung-kim/d146b25ea754e73bf60a to your computer and use it in GitHub Desktop.
Map vs WeakMap performance
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
let looper = (callback) => { | |
let n = 2000000; | |
while (n > 0) { | |
callback(n); | |
n--; | |
} | |
} | |
let timer = (log, callback) => { | |
let start = Date.now(); | |
callback() | |
console.log(log, Date.now() - start); | |
} | |
let map = new Map(); | |
let weakMap = new WeakMap(); | |
let keys = []; | |
for (var m = 0; m < 2000000; m++) { | |
keys.push({}); | |
} | |
timer('Map set took: ', () => looper(index => map.set(keys[Math.floor(Math.random() * 2000000)], index))); | |
timer('weakMap set took: ', () => looper(index => weakMap.set(keys[Math.floor(Math.random() * 2000000)], index))); | |
console.log(); | |
timer('Map int key get took: ', () => looper(index => {let dummylet = map.get(keys[Math.floor(Math.random() * 2000000)]) })); | |
timer('weakMap int key get took: ', () => looper(index => {let dummylet = weakMap.get(keys[Math.floor(Math.random() * 2000000)]) })); | |
// # node v8 sample output | |
// | |
// Map set took: 1781 | |
// weakMap set took: 1278 | |
// | |
// Map int key get took: 723 | |
// weakMap int key get took: 681 |
WeakMap may be use as storage of object's hidden properties.
So i suggest to add this tests:
timer('hidden prop set took: ', () => looper(index => keys[Math.floor(Math.random() * 2000000)]._hidden = index));
timer('hidden prop int key get took: ', () => looper(index => {let dummylet = keys[Math.floor(Math.random() * 2000000)]._hidden }));
Sorry, but this test is awful :D
- You have
Math.floor(Math.random() * 2000000)
inside the test callback. Which is 90% of the recorded execution time for e.g.map.get()
. What you want is to test just the native JS functions nothing else. - The
Math.random()
makes it impossible to compare e.g.map.set()
vs.weakMap.set()
. Date.now()
isms
based, if the loop finishes in less than 1ms or even if you want more precision you get a problem.console.log()
blocks CPU resources because of the async re-render of the output while execution of next test continues.
This works better:
- keys/values are pre calculated (this is not part of the test runtimes)
- All tested methods use the same key/values
performance.now()
for subms
percision- A 2nd loop that re-runs each test function with other values
- Async recording with
PerformanceObserver()
and logging at the end
Sidenote:
Your implementation tests 2Mio iterations (~4s on my browser). With pre-calculated key/values. This test tests 20Mio iterations in ~ 2s on my browser.
console.clear();
let iterations = 5000;
let loopCount = 2000;
let testResult = {};
let logResult = () => {
console.table(
Object.entries(testResult).sort((a,b)=>{
return a[1] - b[1];
}).map(obj=>{
obj[1] /= iterations;
return obj;
})
);
};
let obs = new PerformanceObserver((list, obs)=>{
const entries = list.getEntriesByType("measure");
for (let i = 0; i < entries.length; i++) {
testResult[entries[i].name] ||= 0;
testResult[entries[i].name] += entries[i].duration;
}
logResult();
performance.clearMarks();
obs.disconnect();
});
obs.observe({
entryTypes: ['measure']
});
// Generate random integer
let getRandInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
// Generate an array of random integers in a given range
let randomArrayInRange = (n, min, max) => Array.from(
{ length: n },
() => getRandInt(min, max)
);
// Generate array with point objects
let generatePoints = (n, ...args) => {
let points = [];
for (let i = 0; i < n; i++) {
points.push({
x: getRandInt(...args),
y: getRandInt(...args)
});
}
return points;
}
for (let i = 0; i < iterations; i++) {
let randInts = randomArrayInRange(loopCount, 1, 1000);
let randObjs = generatePoints(loopCount, 1, 1000);
let map = new Map();
let weakMap = new WeakMap();
performance.mark('A');
for (let i = 0; i < randInts.length; i++) {
let v = map.set(randInts[i], randObjs[i]);
}
performance.mark('B');
performance.measure('Map.set', 'A', 'B');
performance.mark('A');
for (let i = 0; i < randInts.length; i++) {
let v = weakMap.set(randObjs[i], randInts[i]);
}
performance.mark('B');
performance.measure('WeakMap.set', 'A', 'B');
performance.mark('A');
for (let i = 0; i < randInts.length; i++) {
let v = map.get(randInts[i]);
}
performance.mark('B');
performance.measure('Map.get', 'A', 'B');
performance.mark('A');
for (let i = 0; i < randInts.length; i++) {
let v = weakMap.get(randObjs[i]);
}
performance.mark('B');
performance.measure('WeakMap.get', 'A', 'B');
performance.mark('A');
for (let i = 0; i < randInts.length; i++) {
Math.floor(Math.random() * 2000000);
}
performance.mark('B');
performance.measure('rand', 'A', 'B');
}
console.table(testResult);
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
With