admin管理员组

文章数量:1180547

I was trying to explore how JavaScript Object performs in comparison to Map or Set for normal key accesses. I ran the below 3 codes on JSBEN.CH.

Objects

const object = {};

for (let i = 0; i < 10000; ++i) {
    object[`key_${i}`] = 1;
}

let result = 0;
  
for (let i = 0; i < 10000; ++i) {
    result += object[`key_${i}`];
}

Maps

const map = new Map();

for (let i = 0; i < 10000; ++i) {
    map.set(`key_${i}`, 1);
}

let result = 0;

for (let i = 0; i < 10000; ++i) {
    result += map.get(`key_${i}`);
}

Sets

const set = new Set();

for (let i = 0; i < 10000; ++i) {
    set.add(`key_${i}`);
}

let result = 0;

for (let i = 0; i < 10000; ++i) {
    result += set.has(`key_${i}`);
}

As you can check in the test link, Map and Set seem to perform almost similar however Objects are much slower every time. Can someone explain what could be the reason that Objects perform worse than Map or Set for basic key access operation?

Edit 1: Just setting keys on Object is also slower than Map/Set.

I was trying to explore how JavaScript Object performs in comparison to Map or Set for normal key accesses. I ran the below 3 codes on JSBEN.CH.

Objects

const object = {};

for (let i = 0; i < 10000; ++i) {
    object[`key_${i}`] = 1;
}

let result = 0;
  
for (let i = 0; i < 10000; ++i) {
    result += object[`key_${i}`];
}

Maps

const map = new Map();

for (let i = 0; i < 10000; ++i) {
    map.set(`key_${i}`, 1);
}

let result = 0;

for (let i = 0; i < 10000; ++i) {
    result += map.get(`key_${i}`);
}

Sets

const set = new Set();

for (let i = 0; i < 10000; ++i) {
    set.add(`key_${i}`);
}

let result = 0;

for (let i = 0; i < 10000; ++i) {
    result += set.has(`key_${i}`);
}

As you can check in the test link, Map and Set seem to perform almost similar however Objects are much slower every time. Can someone explain what could be the reason that Objects perform worse than Map or Set for basic key access operation?

Edit 1: Just setting keys on Object is also slower than Map/Set.

Share Improve this question edited Nov 23, 2023 at 17:50 CPlus 4,59742 gold badges30 silver badges71 bronze badges asked Apr 3, 2021 at 13:11 D_S_XD_S_X 1,5397 gold badges18 silver badges38 bronze badges 13
  • 2 set.add only takes one argument. – Jonas Wilms Commented Apr 3, 2021 at 13:18
  • Most engines have various representations for objects. It might take some time for the engine to find the right one ("to get hot"). – Jonas Wilms Commented Apr 3, 2021 at 13:20
  • The real question is whether the JSBENC is reliable. Have you tried any other performance testers besides JSBENC? – Edison Pebojot Commented Apr 3, 2021 at 13:22
  • 1 They answer the question by saying that a Map loops over the values in order that they were added whereas an object does not. See Map Description and Map vs Object – Get Off My Lawn Commented Apr 3, 2021 at 13:56
  • 1 @D_S_X They are performing better simply because they were optimised for that task, whereas objects are optimised for static lookups on non-changing shapes (while they also support dynamic lookup, it's not their main use case). – Bergi Commented Apr 3, 2021 at 15:47
 |  Show 8 more comments

1 Answer 1

Reset to default 37

Looking at relative numbers only is always dangerous, here are some absolute numbers, run on NodeJS v14.14.0 on an Intel 8350U:

Iterations Object write Object read Map write Map read
100 0ms 0ms 0ms 0ms
1.000 3ms 1ms 0ms 0ms
10,000 7ms 4ms 8ms 1ms
1.000.000 1222ms 527ms 632ms 542ms

So as one can see, for 10.000 iterations the difference between objects and maps is 1 millisecond in the run above, and as that is the accuracy of the time measurement, we can't really derive any conclusion from that test. The results are absolutely random.

For 1 Million iterations one can see a clear advantage of Map writes over Object writes, the read performance is very similar. Now if we look at absolute numbers, this is still one million writes / s. So although object writes are a lot slower, this will unlikely be the bottleneck of your application.

For an accurate explanation, one would have to analyze all the steps the engine performs. For that you can run node --print-code and analyze the bytecode that gets run. I don't have the time for that, though here are some observations:

  1. If the object gets constructed with Object.create(null) (having no prototype) the performance is about the same, so prototype lookup does not influence performance at all.

  2. After the 20th iteration, V8 chooses the internal representation dictionary_map for object, so this is basically one hash map competing with another hashmap (one can run node --allow-natives-syntax and then use %DebugPrint(object) to get the internal representation).

  3. For objects with more than 2 ** 23 keys, write performance degrades even more, see Performance degrade on JSObject after 2^23 items (though maps also can‘t be much larger - see Maximum number of entries in Node.js Map? )

For reference, here is the code used to run the benchmark:

function benchmark(TIMES) {  
  console.log("BENCHMARK ", TIMES);

  const object = Object.create(null);

    let start = Date.now();
    for (let i = 0; i < TIMES; ++i) {
        object[`key_${i}`] = 1;
    }

    console.log("Object write took", Date.now() - start);
    start = Date.now();

    let result = 0;
  
    for (let i = 0; i < TIMES; ++i) {
      result += object[`key_${i}`];
    }

    console.log("Object read took", Date.now() - start);
    start = Date.now();

  


  const map = new Map();
  
  for (let i = 0; i < TIMES; ++i) {
    map.set(`key_${i}`, 1);
  }
  
  console.log("Map write took", Date.now() - start);
  start = Date.now();

  result = 0;
  
  for (let i = 0; i < TIMES; ++i) {
    result += map.get(`key_${i}`);
  }

  console.log("Map read took", Date.now() - start);

}

benchmark(100);
benchmark(1_000);
benchmark(10_000);
benchmark(1_000_000);

To sum up:

  • Use Maps for dictionaries with lots of different, changing keys as they are slightly better than objects internally represented as hash table
  • Use Objects for - well - objects. If you have a low number of keys and frequently access those, Objects are way faster (as the engine can use inline caching, hidden classes with fixed memory layout etc.)

本文标签: Javascript Object vs MapSet key lookup performanceStack Overflow