javascriptperformancejavascript-objectsv8

Initiate all potential props of the object right away or as you go?


I'm my scenario I have to create a lot of objects (100k+). Those objects can 'evolve' in the course if their life. Some will stay practically unchanged, others with will mutate and have a bunch of props assigned.

The most evolved objects might have 30+ properties.

All object are just data, no function, no logic.

From performance point of view, should I use:

const obj = Object.create(null)

obj.prop1 = ...;
obj.prop2 = ...;

//later

obj.prop3 = ...;

//later still

obj.prop4 = ...;

or

cost obj = {
    prop1 = ...;
    prop2 = ...;

    prop3 = undefined;
    prop4 = undefined;
}

Or just define the class and use new.

I'm considering the first approach because:

  1. there might be some memory savings with null prototype (and smaller hashtable?)
  2. it is nicer to iterate over object properties without doing 'hasOwnProperty'.
  3. Also it will be easier for me to debug without a bunch of undefined properties hanging on the list in IDE/DevTools.

At the same time, I vaguely remember that v8 was doing some JIT magic with compilation of objects, but if I continue to add properties overtime, that this optimization could go out of the window.

Which option is better from memory usage and/or performance point of view?


Solution

  • (V8 developer here.)

    For performance: use a class, and create all properties in the constructor, unconditionally.
    A traditional constructor function works too; the key point is that all objects end up having the same shape (in particular: same set of properties). When that's the case, then the generated code for any, say, obj.prop3 property lookup can be specialized to that one object shape, which is faster than having to handle multiple object shapes (even if they all have .prop3 as their third property).

    For memory: of course, smaller objects (with fewer properties) use less memory.
    (Using null as the prototype has no impact on memory consumption in either direction, as the default Object prototype exists anyway, whether your objects use it or not.)

    So ultimately, it's a tradeoff, and only you can decide which approach works best for your app. Depending on your requirements and your data's characteristics, it could in extreme cases even be worthwhile to use a hybrid approach, where e.g. prop1 through prop5 are always present because they're very common, and then there's an all_other_props property that points at a child object (or perhaps a Map) that holds additional rarely-used properties. But it's rare that this matters enough to be worth the hassle.

    When in doubt, try both/several approaches and measure. And when you measure, be sure to measure with your real app, not just a microbenchmark -- microbenchmarks tend to either ignore or over-emphasize things that your real app doesn't care about, so they can easily trick you into making a decision that'll end up being suboptimal. If you can't measure a difference in your real app, then the difference doesn't matter.


    Re comments:

    If prop3 might not be needed, not creating it is almost certainly better performance-wise. It's hard to imagine how doing something can be faster than not doing it.

    It's not about performance of object creation (where, sure, doing more work costs at least slightly more time), it's about performance of code handling those objects later, where (1) it's beneficial when all objects have the same shape, and (2) overwriting an existing property is cheaper than adding a new property.