Why “Map” manipulation is much slower than “Object” in JavaScript (v8) for integer keys?

不想你离开。 提交于 2021-02-10 17:48:01

问题


I was happily using Map for indexed accessed everywhere in my JavaScript codebase, but I've just stumbled upon this benchmark: https://stackoverflow.com/a/54385459/365104

I've re-created it here as well: https://jsben.ch/HOU3g

What benchmark is doing is basically filling a map with 1M elements, then iterating over them.

I'd expect the results for Map and Object to be on par, but they differ drastically - in favor of Object.

Is this expected behavior? Can it be explained? Is it because of the ordering requirement? Or because map is doing some key hashing? Or just because Map allows any object as key (I'd expect it using pointer address for key then, which does not need any hashing)? What is the difference between the Map and Object indexing algorithms?

This is quite unexpected and discouraging - basically I'll have to revert back to the old-school "object as map" coding style.

Update #1

As suggested in comments, the Object might be optimized to Array (since it is indexed by integer, starting from the zero).

Changing the iteration order from size to 0 - Object is still 2x faster. When using strings as index, Map performs 2x better.


回答1:


(V8 developer here.)

I'll have to revert back to the old-school "object as map" coding style.

If you do that, you will have fallen victim to a misleading microbenchmark.

In the very special case of using consecutive integers as keys, a plain Object will be faster, yes. Nothing beats a contiguous array in that scenario. So if the "indexed accesses everywhere in your codebase" that you mentioned are indeed using index sets like the integers from 0 to 1M, then using an Object or Array is a good idea. But that's a special case. If the index space is sparse, things will already look different.

In the general case of using arbitrary strings in random order, a Map will perform significantly better than an Object. Even more importantly, the way such object property accesses are handled (in V8, and quite possibly in other engines too) has non-local effects: if one function puts excessive stress on the slow path of the object property lookup handling system, then that will likely slow down some other functions relying on that same slow path for their property accesses.

The fundamental reason is that engines optimize different things for different usage patterns. An engine could implement Objects and Maps pretty much the same under the hood; but that wouldn't be the ideal behavior, because different usage patterns benefit from different internal representations and implementation choices. So engines allow you to provide them with a hint: if you use a Map, the engine will know that you're planning to use the thing as a map (duh!), where random keys will come and go. If you use an Object, then the engine will (at least at first) assume that you want the set of optimizations that work best for your average object, where the set of properties is fairly small and static. If you use an Array (or Object with only integer properties, which is nearly the same thing in JS), then you're making it easy for the engine to give you fast integer-indexed accesses.

Using "x" + i as key is a good suggestion to demonstrate how quickly a microbenchmark can be changed so it appears to produce opposite results. But here's a spoiler: if you do (only) this modification, then a large part of what you're measuring will be number-to-string conversion and string internalization, not Map/Object access performance itself.

Beware of microbenchmarks; they are misleading. You really have to analyze them quite deeply (by profiling, and/or by inspecting generated code, and/or by tracing other engine internals) to be sure that they're measuring what you think they're measuring, and hence are producing results that are telling you what you think they're telling you.

In general, it is strongly recommended to use representative test cases for performance measurements. Ideally, your app itself; or by extracting a realistic part of it into a testcase operating on realistic data. And if you can't measure a difference between two implementation choices with a stress test for your entire production app, then it's not a difference worth worrying about it. With a microbenchmark (i.e. a couple of artificially crafted lines), I can "prove" almost anything that doesn't apply to the general case.



来源:https://stackoverflow.com/questions/62350146/why-map-manipulation-is-much-slower-than-object-in-javascript-v8-for-integ

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!