Sanity Check: Object Creation Performance

[Update: Seriously, go read this post by Vyacheslav Egorov @mraleph. It's the most fantastic thing I've read in a long, long time!]

I hear all kinds of myths and misconceptions spouted as facts regarding the performance characteristics of various object creation patterns in JavaScript. The problem is that there’s shreds of facts wrapped up in all kinds of conjecture and, frankly, FUD (fear-uncertainty-doubt), so separating them out is very difficult.

So, I need to assert() a couple of things here at the beginning of this post, so we’re on the same page about context.

  1. assert( "Your app creates more than just a few dozen objects" ) — If your application only ever creates a handful of objects, then the performance of these object creations is pretty much moot, and you should ignore everything I talk about in this post. Move along.
  2. assert( "Your app creates objects not just on load but during critical path operations." ) — If the only time you create your objects is during the initial load, and (like above) you’re only creating a handful of these, the performance differences discussed here are not terribly relevant. If you don’t know what the critical path is for your application, stop reading and go figure that out first. If you use an “object pool” where you create objects all at once upfront, and use these objects later during run-time, then consult the first assertion.

Let me make the conclusions of those assertions clear: if you’re only creating a few objects (and by that, I mean 100 or less), the performance of creating them is basically irrelevant.

If you’ve been led to believe you need to use prototype, new, and class style coding in JS to get maximum object creation performance for just a couple of objects, you need to set such silliness aside. Come back to this article if you ever need to create lots of objects.

I suspect the vast majority of you readers don’t need to create hundreds or thousands of objects in your application. In my 15 year JS dev career, I’ve seen a lot of apps in a lot of different problem domains, but it’s been remarkably rare how often JavaScript apps legitimately need to create enough objects where the performance of such creation was something to get worked up about.

But, to read other blogs and books on the topic, just about every JavaScript application needs to avail itself of complicated object “inheritance” heirarchies to have any hope of reasonable performance.

Hogwash.

Object Creations Where Performance Isn’t Relevant

Let’s list some examples of object creation needs where the performance differences will never be a relevant concern of yours, beyond “premature micro optimizations”:

  • You have some UI widgets in your page, like a calendar widget, a few smart select drop-downs, a navigation toolbar, etc. In total, you have about a dozen objects (widgets) you create to build the pieces of your UI. Most of them only ever get created once at load time of your app. A few of them (like smart form elements) might get recreated from time to time, but all in all, it’s pretty lightweight on object creations in your app.
  • You create objects to represent your finite data model in your application. You probably have less than 10 different data-domains in your whole application, which probably means you have at most one object per domain. Even if we were really liberal and counted sub-objects, you have way less than 50 total objects ever created, and few if any of them get recreated.
  • You’re making a game with a single object for each character (good guy, enemy, etc). At any given time, you need 30 or fewer objects to represent all these characters. You can (and should) be creating a bunch of objects in an object pool at game load, and reusing objects (to avoid memory churn) as much as possible. So, if you’re doing things properly, you actually won’t be doing an awful lot of legitimate object creation.

Are you seeing the pattern? There’s a whole lot of scenarios where JS developers have commonly said they need to absolutely maxmimize object creation performance, and there’s been plenty of obsession on micro-optimizations fueled by (irresponsible and misleading) micro-benchmarks on jsPerf to over-analyze various quirks and niches.

But in truth, the few microseconds difference between the major object creation patterns when you only have a dozen objects to ever worry about is so ridiculously irrelevant, it’s seriously not even worth you reading the rest of this post if that’s the myth you stubbornly insist on holding strong to.

Object Creation On The Critical Path

Let me give a practical example I ran into recently where quite clearly, my code was going to create objects in the critical path, where the performance definitely matters.

Native Promise Only is a polyfill I’ve written for ES6 Promises. Inside this library, every time a new promise needs to be created (nearly everything inside of promises creates more promises!), I need to create a new object. Comparatively speaking, that’s a lot of objects.

These promise objects tend to have a common set of properties being created on them, and in general would also have a shared set of methods that each object instance would need to have access to.

Moreover, a promise library is designed to be used extensively across an entire application, which means it’s quite likely that some or all of my code will be running in the critical path of someone’s application.

As such, this is a perfect example where paying attention to object creation performance makes sense!

Simple Objects

Let’s draw up an app scenario and use it to evaluate various object creation options: a drawing application, where each line segment or shape someone draws on the canvas will be individually represented by a JS object with all the meta data in them.

On complex drawing projects, it wouldn’t be at all surprising to see several thousand drawing elements placed on the canvas. Responsiveness while freehand drawing is most definitely a “critical path” for UX, so creating the objects performantly is a very reasonable thing to explore.

OK, let’s examine the first object creation pattern — the most simple of them: just simple object literals. You could easily do something like:

function makeDrawingElement(shapeType,coords,color) {
    var obj = {
        id: generateUniqueId(),
        type: shapeType,
        fill: color,
        x1: coords.x1,
        y1: coords.y1,
        x2: coords.x2,
        y2: coords.y2,

        // add some references to shared shape utilities
        deleteObj: drawing.deleteObj,
        moveForward: drawing.moveForward,
        moveBackward: drawing.moveBackward
    };

    return obj;
}

var el = makeDrawingElement(
    "line",
    { x1:10, y1:10, x2:50, y2:100 },
    "red"
);

OK, so all we’re doing here is creating a new object literal each time we create a new shape on the drawing surface. Easy enough.

But it’s perhaps the least performant of the various options. Why?

As an internal implementation detail, it’s hard for JS engines to find and optimize repeated object literals as shown here. Moreover, as you can see, we’re copying function references (not functions themselves!) onto each object.

Let’s see some other patterns which can address those concerns.

Hidden Classes

It’s a famous fact that JavaScript engines like v8 track what’s called a “hidden class” for an object, and as long as you’re repeatedly creating new objects of this same “shape” (aka “hidden class”), it can optimize the creation quite a bit.

So, it’s common for people to suggest creating objects like this, to take advantage of “hidden class” implementation optimizations:

function DrawingElement(shapeType,coords,color) {
    this.id = generateUniqueId();
    this.type = shapeType;
    this.fill = color;
    this.x1 = coords.x1;
    this.y1 = coords.y1;
    this.x2 = coords.x2;
    this.y2 = coords.y2;

    // add some references to shared shape utilities
    this.deleteObj = drawing.deleteObj;
    this.moveForward = drawing.moveForward;
    this.moveBackward = drawing.moveBackward;
}

var el = new DrawingElement(
    "line",
    { x1:10, y1:10, x2:50, y2:100 },
    "red"
);

Simple enough of a change, right? Well, depends on your perspective. It seems like an interesting and strange “hack” that we have to add our properties to a this object created by a new Fn(..) call just to opt into such magical optimizations.

In fact, this “hidden class” optimization is often mis-construed, which contributes to its “magical” sense.

It’s true that all those new DrawingElement(..) created object instances should share the same “hidden class”, but the more important optimization is that because DrawingElement(..) is used as a somewhat declarative constructor, the engine can estimate before even the first constructor call how many this.prop = .. assignments will happen, so it knows generally how “big” to pre-size the objects that will be made by the “hidden class”.

As long as this DrawingElement(..) constructor adds all the properties to the this object instance that are ever going to be added, the size of the object instance won’t have to grow later. This leads to the best-case performance in that respect.

The somewhat declarative nature of new Fn(..) and this aids in the optimization estimates, but it also invokes the implication that we’re actually doing class-oriented coding in JS, and it thus invites that sort of design abstraction on top of our code. Unfortunately, JS will fight you from all directions in that effort. Building class-like abstractions on top of your code will often hurt your performance, not help it.

Shared .prototype Methods

Many developers will cite that a further problem performance-wise (that is, memory usage, specifically) with this code is the copying of function references, when we could instead use the [[Prototype]] “inheritance” (more accurately, delegation link) to share methods among many object instancess without duplication of function references.

So, we keep building on top of the “class abstraction” pattern with code like this:

function DrawingElement(shapeType,coords,color) {
    this.id = generateUniqueId();
    this.type = shapeType;
    this.fill = color;
    this.x1 = coords.x1;
    this.y1 = coords.y1;
    this.x2 = coords.x2;
    this.y2 = coords.y2;
}

// add some references to shared shape utilities
// aka, "defining the `DrawingElement` "class" methods
DrawingElement.prototype.deleteObj = drawing.deleteObj;
DrawingElement.prototype.moveForward = drawing.moveForward;
DrawingElement.prototype.moveBackward = drawing.moveBackward;

var el = new DrawingElement(
    "line",
    { x1:10, y1:10, x2:50, y2:100 },
    "red"
);

Now, we’ve placed the function references on DrawingElement.prototype object, which el will automatically be [[Prototype]] linked to, so that calls such as el.moveForward() will continue to work as before.

So… now it certainly looks like we’ve fully embraced a DrawingElement “class”, and we’re “instantiating” elements of this class with each new call. This is the gateway drug of complexity of design/abstraction that will lead you perhaps to later try to do things like this:

function DrawingElement(..) { .. }

function Line(..) { DrawingElement.call( this ); }
Line.prototype = Object.create( DrawingElement );

function Shape(..) { DrawingElement.call( this ); }
Shape.prototype = Object.create( DrawingElement );
..

Be careful! This is a slippery slope. You will very quickly create enough abstraction here to completely erase any potential performance micro-gains you may be getting over the normal object literal.

Objects Only

I’ve written extensively in the past about an alternate simpler pattern I call “OLOO” (objects-linked-to-other-objects), including the last chapter of my most recent book: “You Don’t Know JS: this & Object Prototypes”.

OLOO-style coding to approach the above scenario would look like this:

var DrawingElement = {
    init: function(shapeType,coords,color) {
        this.id = generateUniqueId();
        this.type = shapeType;
        this.fill = color;
        this.x1 = coords.x1;
        this.y1 = coords.y1;
        this.x2 = coords.x2;
        this.y2 = coords.y2;
    },
    deleteObj: drawing.deleteObj,
    moveForward: drawing.moveForward,
    moveBackward: drawing.moveBackward
};

var el = Object.create( DrawingElement );
// notice: no `new` here
el.init(
    "line",
    { x1:10, y1:10, x2:50, y2:100 },
    "red"
);

This OLOO-style approach accomplishes the exact same functionality/capability as the previous snippets. Same number and shape of objects as before.

Instead of the DrawingElement.prototype object belonging to the DrawingElement(..) constructor function, with OLOO, an init(..) function method belongs to the DrawingElement object. Now, we don’t need to have any references to .prototype, nor any usage of the new keyword.

This code is simpler to express.

But is it faster, slower, or the same as the previous “class” approach?

TL;DR: It’s much slower. <frowny-face>

Unfortunately, the OLOO-style implicit object initialization (using init(..)) is not declarative enough for the engine to make the same sort of pre-“guesses” that it can with the constructor form discussed earlier. So, the this.prop = .. assignments are likely expanding the object storage several times, leading to extra churn of those “hidden classes”.

Also, it’s undeniable that with OLOO-style coding, you make an “extra” call, by separately doing Object.create(..) plus el.init(..). But is that extra call a relevant performance penalty? It seems Object.create(..) is in fact a bit slower.

On the good side, in both cases, there’s no copied function references to each instance, but instead just shared methods we [[Prototype]] delegate to, so at the very least, THAT OPTIMIZATION is also in play with both styles of coding.

Performance Benchmark: OO vs OLOO

The problem with definitively quantifying the performance hit of OLOO-style object creation is that micro-benchmarking is famously flawed on such issues. The engines do all kinds of special optimizations that are hard to “defeat” to isolate and test accurately.

Nevertheless, I’ve put together this test to try to get some kind of reasonable approximation. Just take the results with a big grain of salt:

So, clearly, OLOO is a lot slower according to this test, right?

Not so fast. Let’s sanity check.

Importantly, context is king. Look at how fast both of them are running.

In recent Chrome, OLOO is running up to ~2.3million ops/sec. If you do the math, that’s 2 per microsecond. IOW each creation operation takes ~430 nanoseconds. That’s still insanely damn fast, if you ask me.

What about classical OO-style? Again, in recent Chrome, ~30million objects created per second. 30 per microsecond. IOW each creation operation is taking ~33 nanoseconds. So… we’re talking about the difference between an object creation taking 33 nanoseconds and 420 nanoseconds.

Let’s look at it more practically: say your drawing app needs to create 10,000 objects (a pretty significantly complex drawing, TBH!). How long will each approach take? Classical OO will take ~330 microseconds, and OLOO will take ~4.3 milliseconds.

That’s a difference of 4 milliseconds in an absolutely worst-case sort of scenario. That’s ~1/4 of the average screen refresh cycle (16.7ms for 60fps). You could re-create all ten thousand objects three or four times, all at once in a row, and still only drop at most a single animation frame.

In short, while “OLOO is 90% slower” seems pretty significant, I actually think it’s probably not terribly relevant for most apps. Only the rarest of apps would ever be creating several tens of thousands of objects per second and needing to squeeze out a couple of milliseconds per cycle.

Summary

First, and foremost, if you’re not creating thousands of objects, stop obsessing about object creation performance.

Secondly, even if you are creating ten thousand objects at once, 4 extra milliseconds in the worst case on the critical path is not likely to bring your app to its knees.

Knowing how to identify the cases where you need (and don’t need!) to optimize your object creations, and how to do so, is very important to the mature JavaScript developer. Don’t just listen to the hype. Give it some sane, critical thought.