get (v2)

Revision 2 of this benchmark created on


Preparation HTML

  <script src="https://cdn.jsdelivr.net/npm/dequal@2.0.3/dist/index.min.js"></script>
  <script src="https://unpkg.com/equivalent-key-map/dist/equivalent-key-map.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/immutable@4.3.7/dist/immutable.min.js"></script>

Setup

class DeepMapC {
      map = new Map();

      constructor(equals = dequal.dequal) {
        this.equals = equals;
      }

      findKey(key) {
        for (let k of this.map.keys()) {
          if (this.equals(k, key)) return k;
        }
      }

      set(key, value) {
        this.map.set(this.findKey(key) ?? key, value);
        return this;
      }

      get(key) {
        const existingKey = this.findKey(key);
        if (existingKey) return this.map.get(existingKey);
      }

      has(key) {
        return !!this.findKey(key);
      }

      delete(key) {
        const existingKey = this.findKey(key);
        if (existingKey) return this.map.delete(existingKey);
        else return false;
      }

      clear() {
        this.map.clear();
      }

      get size() {
        return this.map.size;
      }

      keys() {
        return this.map.keys();
      }

      values() {
        return this.map.values();
      }

      entries() {
        return this.map.entries();
      }

      [Symbol.iterator]() {
        return this.map[Symbol.iterator]();
      }
    }

    class DeepMapS {
      map = new Map();
      keyMap = new Map();

      constructor(serialize, initialValues) {
        this.serialize = serialize;
        if (initialValues) {
          for (const [key, value] of initialValues) this.set(key, value);
        }
      }

      set(key, value) {
        const keyStr = this.serialize(key);
        this.keyMap.set(keyStr, key);
        this.map.set(keyStr, value);
        return this;
      }

      get(key) {
        return this.map.get(this.serialize(key));
      }

      has(key) {
        return this.keyMap.has(this.serialize(key));
      }

      delete(key) {
        const keyStr = this.serialize(key);
        this.keyMap.delete(keyStr);
        return this.map.delete(keyStr);
      }

      clear() {
        this.map.clear();
        this.keyMap.clear();
      }

      get size() {
        return this.map.size;
      }

      keys() {
        return this.keyMap.values();
      }

      values() {
        return this.map.values();
      }

      forEach(cb, thisArg) {
        this.keyMap.forEach((key, keyStr) => cb.call(thisArg, this.map.get(keyStr), key, this));
      }

      copy() {
        const newMap = new DeepMap(this.serialize);
        // directly copying the map and keymap (instead of passing it to the constructor), avoids having to recalculate each key hash
        newMap.map = new Map(this.map);
        newMap.keyMap = new Map(this.keyMap);
        return newMap;
      }

      *entries() {
        for (let [keyStr, key] of this.keyMap.entries()) {
          yield [key, this.map.get(keyStr)];
        }
      }

      [Symbol.iterator]() {
        return this.entries();
      }
    }

    class DeepMapO {
      map = {};
      keyMap = {};

      constructor(serialize, initialValues) {
        this.serialize = serialize;
        if (initialValues?.[Symbol.iterator]) {
          for (const [key, value] of initialValues) this.set(key, value);
        }
      }

      set(key, value) {
        const keyStr = this.serialize(key);
        this.keyMap[keyStr] = key;
        this.map[keyStr] = value;
        return this;
      }

      get(key) {
        return this.map[this.serialize(key)];
      }

      has(key) {
        return !!this.map[this.serialize(key)];
      }

      delete(key) {
        const keyStr = this.serialize(key);
        const hasKey = !!this.map[keyStr];
        delete this.keyMap[hasKey];
        delete this.map[keyStr];
        return hasKey;
      }

      clear() {
        this.map = {};
        this.keyMap = {};
      }

      get size() {
        return Object.keys(this.map).length;
      }

      *keys() {
        for (let keyStr in this.keyMap) yield this.keyMap[keyStr];
      }

      *values() {
        for (let keyStr in this.keyMap) yield this.map[keyStr];
      }

      *entries() {
        for (let keyStr in this.keyMap) yield [this.keyMap[keyStr], this.map[keyStr]];
      }

      forEach(cb, thisArg) {
        for (let keyStr in this.keyMap) cb.call(thisArg, this.map[keyStr], this.keyMap[keyStr], this);
      }

      copy() {
        const newMap = new DeepMapO(this.serialize);
        // directly copying the map and keymap (instead of passing it to the constructor), avoids having to recalculate each key hash
        newMap.map = { ...this.map };
        newMap.keyMap = { ...this.keyMap };
        return newMap;
      }

      [Symbol.iterator]() {
        return this.entries();
      }
    }

    const serialize = k => `${k.chain}}_{${k.joint}`;
    const deSerialize = str => {
      const parts = str.split('}_{');
      return { chain: parts[0], joint: parts[1] };
    };

    const map = new Map();
    const obj = {};
    const mapIsEqual = new DeepMapC(dequal.dequal);
    const deepMapMap = new DeepMapS(serialize);
    const deepMapObj = new DeepMapO(serialize);
    const equivalentKeyMap = new EquivalentKeyMap();
    let immutableMap = new Immutable.Map();

    for (let chain = 0; chain < 5; chain++) {
      for (let joint = 0; joint < 10; joint++) {
        const key = {
          chain: `cf221f55-957c-43bf-be45-f5be6d2cebe4-${chain}`,
          joint: `dc7d8abf-871c-4980-84ed-c7049a67558d-${joint}`
        };
        const value = `value-${chain}-${joint}`;
        map.set(serialize(key), value);
        obj[serialize(key)] = value;
        equivalentKeyMap.set(key, value);
        immutableMap = immutableMap.set(Immutable.fromJS(key), value);
        mapIsEqual.set(key, value);
        deepMapMap.set(key, value);
        deepMapObj.set(key, value);
      }
    }

    const testKey = {
      chain: `cf221f55-957c-43bf-be45-f5be6d2cebe4-${4}`,
      joint: `dc7d8abf-871c-4980-84ed-c7049a67558d-${8}`
    };

Test runner

Ready to run.

Testing in
TestOps/sec
native map
return map.get(serialize(testKey))
ready
native obj
return obj[serialize(testKey)]
ready
equivalentKeyMap
return equivalentKeyMap.get(testKey)
ready
immutableMap
return immutableMap.get(Immutable.fromJS(testKey))
ready
deepMap deep isEqual
return mapIsEqual.get(testKey)
ready
deepMap map
return deepMapMap.get(testKey)
ready
deepMap obj
return deepMapObj.get(testKey)
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.