Skip to content

Instantly share code, notes, and snippets.

@jung-kim
Last active December 9, 2022 08:43
Show Gist options
  • Save jung-kim/d146b25ea754e73bf60a to your computer and use it in GitHub Desktop.
Save jung-kim/d146b25ea754e73bf60a to your computer and use it in GitHub Desktop.
Map vs WeakMap performance
"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
@mihai1voicescu
Copy link

With

let looper = (callback) => {
    let n = 20000000;
Map set took:  13682
weakMap set took:  10862

Map int key get took:  1767
weakMap int key get took:  10292
$ node -v
v14.4.0

@khokm
Copy link

khokm commented Jun 30, 2021

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

@exodus4d
Copy link

exodus4d commented Jul 13, 2021

Sorry, but this test is awful :D

  1. 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.
  2. The Math.random() makes it impossible to compare e.g. map.set() vs. weakMap.set() .
  3. Date.now() is ms based, if the loop finishes in less than 1ms or even if you want more precision you get a problem.
  4. console.log() blocks CPU resources because of the async re-render of the output while execution of next test continues.

This works better:

  1. keys/values are pre calculated (this is not part of the test runtimes)
  2. All tested methods use the same key/values
  3. performance.now() for sub ms percision
  4. A 2nd loop that re-runs each test function with other values
  5. 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