1

I'm considering migrating my state management layer to using Map versus using a standard object.

From what I've read, Map is effectively a hash table whereas Objects use hidden classes under the hood. Generally it's advised, where the properties are likely to be dynamically added or removed it's more efficient to use Map.

I set up a little test and to my surprise accessing values in the Object version was faster.

https://jsfiddle.net/mfbx9da4/rk4hocwa/20/

enter image description here

The article also mentions fast and slow properties. Perhaps the reason my code sample in test1 is so fast is because it is using fast properties? This seems unlikely as the object has 100,000 keys. How can I tell if the object is using fast properties or dictionary lookup? Why would the Map version be slower?

And yes, in practice, looks like a premature optimization, root of all evil ... etc etc. However, I'm interested in the internals and curious to know of best practices of choosing Map over Object.

david_adler
  • 9,690
  • 6
  • 57
  • 97
  • V8 employs a lot of very sophisticated optimizations for simple object properties, because in *most* JavaScript on the web today that is a huge portion of the workload. – Pointy Jan 25 '21 at 14:19
  • 1
    Also, unless you really need arbitrary data types for map keys, I don't know what the point of migrating to Map would be. – Pointy Jan 25 '21 at 14:20
  • Also also, where is it "advised" that it's more efficient to use Map? – Pointy Jan 25 '21 at 14:21
  • Another question is, are you running code where a difference of 54.42 ops/sec when retrieving data from object/Maps with hundreds of thousands of keys would be tangible and of great concern? I would be worried about any developer that came to me with a code review of an object with 100000 keys, but that's me. I think this is a case of premature optimization. – Heretic Monkey Jan 25 '21 at 14:30
  • I do genuinely have 100000 keys in my current application. It involves a messaging app where all messages are stored on device which seems like a reasonable use case. And yes you're right, this is not my current bottleneck. I am not experiencing performance issues and hoping that switching to Map will solve all my problems. – david_adler Jan 25 '21 at 14:45
  • What is your state management system that you use? – Konstantin Jan 25 '21 at 14:45
  • @Konstantin redux / easy-peasy https://easy-peasy.now.sh/ – david_adler Jan 25 '21 at 14:45
  • I was researching this topic last week and stumbled upon this GH issue, do recommend a reading before taking a decision https://github.com/reduxjs/redux/issues/1499 – Konstantin Jan 25 '21 at 14:48

1 Answers1

3

(V8 developer here.)

Beware of microbenchmarks, they are often misleading.

V8's object system is implemented the way it is because in many cases it turns out to be very fast -- as you can see here.

The primary reason why we recommend using Map for map-like use cases is because of non-local performance effects that the object system can exhibit when certain parts of the machinery get "overloaded". In a small test like the one you have created, you won't see this effect, because nothing else is going on. In a large app (using many objects with many properties in many different usage patterns), it's still not guaranteed (because it depends on what the rest of the app is doing) but there's a good chance that using Maps where appropriate will improve overall performance -- if the overall system previously happened to run into one of the unfortunate situations.

Another reason is that Maps handle deletion of entries much better than Objects do, because that's a use case their implementation explicitly anticipates as common.

That said, as you already noted, worrying about such details in the abstract is a case of premature optimization. If you have a performance problem, then profile your app to figure out where most time is being spent, and then focus on improving those areas. If you do end up suspecting that the use of objects-as-maps is causing issues, then I recommend to change the implementation in the app itself, and measure (with the real app!) whether it makes a difference.

(See here for a related, similarly misleading microbenchmark, where even the microbenchmark itself started producing opposite results after minor modifications: Why "Map" manipulation is much slower than "Object" in JavaScript (v8) for integer keys?. That's why we recommend benchmarking with real apps, not with simplistic miniature scenarios.)

jmrk
  • 34,271
  • 7
  • 59
  • 74
  • Would you say the benchmarks in this article are unrealistic? https://community.risingstack.com/the-worlds-fastest-javascript-memoization-library/ – david_adler May 08 '21 at 16:04
  • 1
    Depends. Looks like all those benchmarks used a fibonacci function, which in particular means all cache keys were small integers (`15` or less, AFAICS). So if your cache is only ever going to see the integers 1 through 15 as keys, then those benchmarks are very realistic. If you have other types of cache keys, or larger numbers of them, or if you delete them sometimes, then tiny-integer based benchmarks are obviously not representative. – jmrk May 08 '21 at 17:13
  • Yeah I also meant in relation to the two other points that you mention. Specifically, the test will not be representative of non local performance impacts of object and the test will not be representative of deleting keys. – david_adler May 08 '21 at 20:02
  • For reference the benchmarking code https://github.com/caiogondim/fast-memoize.js/tree/master/benchmark/cache – david_adler May 08 '21 at 21:08
  • Apologies, I realise you addressed the key deletion is not representative. How would you go about benchmarking overloading of the system? Would you have to have a heap size close to the available RAM? – david_adler May 08 '21 at 21:32
  • 1
    No, it's not about heap size. One issue you can run into is throwing too many (a couple thousand) combinations of {hidden class, property name} at the "megamorphic" caching system. Another issue is that having many hidden class changes torpedoes the benefits of optimizing code (or, because of that, ends up disabling optimization). It's not super easy to recreate these situations, and I'm also deliberately being somewhat vague because the details change over time; the point is that applications do sometimes run into these, and using `Map`s for maps can go a long way towards avoiding that. – jmrk May 08 '21 at 22:21
  • Thanks, is there anyway to easily profile the megamorphic cache size of a given web page? – david_adler May 08 '21 at 23:14
  • No. (It's got a fixed size, but there's no easy way to profile its load/overload situation either.) Look, don't worry about engine implementation details, if you're happy with the performance that objects-as-maps give you, then just ignore my generic advice; and if you do have a performance problem, then simply try and see whether Maps help in your app's specific situation. For a single massive object where only a few properties are read, it's probably fine either way. – jmrk May 09 '21 at 02:12