Search code examples
javascriptperformanceoptimizationprototypegetter

How does extending the prototype chain increase performance in this case?


I've had a long-standing assumption that deep prototype chains resulted in performance deterioration for property accessors. I was trying to explain that on hide the getter or add in the proto Object when a quick benchmark I threw together resulted in quite the opposite outcome from what I was expecting.

What is going on here? Am I missing something obvious, or does this outright demonstrate that my (and others') assumption about the performance of property accessors on the prototype chain was wrong?

Setup

const object1 = {
  foo: 'Hello, World!',
  get bar () { return this.foo }
};

const object2 = Object.assign(Object.create({
  get bar () { return this.foo }
}), {
  foo: 'Hello, World!'
});

let result;

Test 1

(control, without prototype)

result = object1.bar;

Test 2

(experiment, with prototype)

result = object2.bar;

Outcome

Test 1 ran 92.85% slower than Test 2, which means that placing get bar () {} in the prototype chain rather than in the object's own properties results in a 14x speed increase for the property accessor. See Object.create() to understand how the layout of the object is different.

Test 1

79,323,722 ops/s ±0.34%

Test 2

1,108,762,737 ops/s ±0.15%

Tested on Windows 10 Intel i7-7700K CPU @ 4.20GHz using Google Chrome 63.0.3239.132 (Official Build) (64-bit)


Solution

  • To my knowledge, these details only apply to the V8 engine, I am not sure how directly this maps to the Firefox's implementation.

    Without the prototype, V8 is creating hidden classes to support the properties of your object. For each new property, a new hidden class is created, and then a transition from the previous hidden class to the new one is created.

    However, this does not happen with prototypes, and is kind of a little known fact from the conversations I have had with regards to the topic. In other words, yes, prototypes are faster.

    To optimize prototypes, V8 keeps track of their shape differently from regular transitioning objects. Instead of keeping track of the transition tree, we tailor the hidden class to the prototype object, and always keep it fast -Toon Verwaest (V8 dev)

    This setup all happens during Dynamic Machine Code Generation. The difference between the two setups that you are seeing is the difference between the more complex hidden class path versus the more custom path. Or, by name, the difference between the fastPropertiesWithPrototype object and the slowProperties object, the latter of which uses dictionary mode.