JS: Benchmarking Lazy Getters

Some Background

The Pattern

Lazy Override

class LazyOverride {
get value() {
let value = Math.random();
Object.defineProperty(this, 'value', {value});
return value;
}
}

Lazy Known

class LazyKnown {
constructor() {
this._value = null;
}
get value() {
return this._value || (this._value = fn());
}
}
  • there is a constructor that needs to set a default value to the pseudo-hidden property.
  • the getter will be invoked every single time the property is accessed, as opposite of becoming the property itself, without prototype round-trip.

Object Literal

const getRandom = () => Math.random();function Literal() {
let value = null;
return {
get value() {
return value || (value = getRandom());
}
};
}
  • functional programming oriented developer don’t even understand the need for classes.
  • old school Web developers know that classes were never a thing, and ES2015 ruined the Web introducing these, because good old prototypal inheritance was enough.
  • v8 and Chrome optimize the hell out of object literals, so object literals is the only thing we should use!

Round #1

benchmark for these patterns
  • how long did it take to create all instances?
  • how long did it take to access that value property for the first time, per each instance?
  • how long did it take, once the property was accessed already, to retrieve back the same data?
  • how long did it take to repeatedly get such data every next execution time?

A first look

When does this matter

same patterns on a Raspberry Pi
  • Lazy Known, aka shaped, won pretty much everywhere, meaning that describing what the object properties will be wins, optimized at the engine level almost more than anything else
  • Lazy Override, aka shadowed, won in terms of repeatedly property access, and 12ms vs 16ms means concretely nothing in a 200K array of instances
  • Object literals lost almost any advantage whatsoever: heavier on the heap, much slower on creation, slightly faster than shaped instances on first access, but also irrelevantly faster in there for a 200K array

And the winner is …

About Sub Patterns

  • JS offers private properties these days, so that using an underscore prefix convention to mean such property is private, and it shouldn’t be addressed directly outside its class definition, feels too much like 90s’
  • JS previously offered the Symbol primitive, which is enumerable like any other property, but it grants no name clashing, plus it’s harder to have in our way out there, yet it’s a direct property like _prop would be.

Private Properties

const getRandom = () => Math.random();class LazyGetterPrivate {
#value = null;
get value() {
return this.#value || (this.#value = getRandom());
}
}

Symbol Properties

const VALUE = Symbol('value');const setSymbol = self => {
let value = Math.random();
self[VALUE] = value;
return value;
};
class LazyGetterSymbol {
constructor() {
this[VALUE] = null;
}
get value() {
return this[VALUE] || setSymbol(this);
}
}

Round #2

shaped VS symbol VS private
  • between shaped and symbol strategy, nobody really wins: repeated runs of the same benchmark sees one, or the other, win for a very irrelevant margin, in terms of creation, first access, or repeated one.
  • private properties add nothing on the heap side, which is some awesome news, but it offers nothing more in terms of performance, and if the goal is to improve lazy properties access, the extra cost on creation becomes something not-acceptable, as the whole goal here is to be as fast as possible in creation, and eventually slightly slower on property access.

The Verdict

  • never trust your laptop is meaningful for a specific benchmark, try to benchmark on slower devices to have a better, realistic, view, of your conclusions.
  • don’t overdo lazy accessors, and use any extra pseudo-private property strategy to obtain best performance.
  • don’t believe classes-less JS is faster or better at anything, in general.
  • don’t over-do lazy accessors, and fallback to what you’ve always known, to day, was very fast, and easy to deal with, that being _prefixed properties, or, maybe, Symbol('name') one!

What about other engines?

jsc / JavaScript Core / WebKit
gjs / SpiderMonkey
NodeJS 12
NodeJS 10
NodeJS 8
NodeJS 6

--

--

--

Web, Mobile, IoT, and all JS things since 00's. Formerly JS engineer at @nokia, @facebook, @twitter.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

A Complete Guide on Flat List.

Events and Listeners in JavaScript

Server side vs Client side rendering : SEO Optimization

So You Want to Use JavaScript in The Next Freelance Project — Prototype(2)

Proxy in javascript

React Fundamentals what you need to know

FCC Speedrun: Wikipedia Viewer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Andrea Giammarchi

Andrea Giammarchi

Web, Mobile, IoT, and all JS things since 00's. Formerly JS engineer at @nokia, @facebook, @twitter.

More from Medium

Intercepting component state to ensure smooth animated transitions

Performance boost for Remirror positioners

UI frameworks and Media Elements 🎧

Getting started with Cypress.io