The Assumptions of the Hidden Class

oculus42

Samuel Rouse

Posted on November 9, 2023

The Assumptions of the Hidden Class

Don't violate the assumptions of the hidden class!

No, this is not about a shadow government conspiracy. We're talking JavaScript performance!

JavaScript doesn't have types but we think of these objects as having particular shapes.
— Seth Thompson

While JavaScript objects are infinitely flexible, we often create simple, reusable pieces like classes or collections – arrays of objects that usually have the same keys and data types. Some languages represent these as structures, but in JavaScript we often talk about the "shape" of objects.

The Hidden Class

In 2015, Seth Thompson spoke at the Chrome Dev Summit about updates to the V8 JavaScript Engine. The video provides a lot of insight into how all JavaScript engines work. The major point for me is the performance improvement possible when an engine can strip away the powerful-yet-complex capabilities of objects and translate them into simple shapes. Google refers to these as "hidden classes".

Computers do a better job at processing data when it is regular, or consistent. If you have a set of objects containing numbers for x, y, and z coordinates, they can be stored and accessed efficiently as a structure of double-precision floats (More about Numbers in JS).

// How a "coordinates" object might be represented in C++
struct coord {
  double x;
  double y;
  double z;
};
Enter fullscreen mode Exit fullscreen mode

The Assumptions

We won’t see that in our code, but the engine can make an assumption about the data and create a hidden class which works like a struct. Engine assumptions don’t stop at objects, either. They can optimize functions with consistent arguments and return types. This can eliminate a lot of type checking and coercion logic that happens internally with dynamic types.

Making these assumptions is often part of writing code anyway. IDEs, JSDoc, TypeScript, and other tools help manage types to avoid unexpected application behavior. These tools and patterns support the assumptions engines make to improve performance.

The Violations

Those hidden classes can provide a lot of performance benefit in our code, but we have to follow some guidelines or risk slowdowns. Because JavaScript is very flexible, we might create a simple function as an example.

const getLength = (entry) => entry.length;

getLength('Hello'); // 5
getLength([1,2,3]); // 3
Enter fullscreen mode Exit fullscreen mode

Both strings and arrays have a length property, so we can write one function to use for both! However, the engine can’t compile a string-only function or an array-only function if we do this. It could receive different types, and possibly return different types!

getLength(3); // undefined
getLength({ length: 'hi!' }); // 'hi!'
getLength(null); // Throws TypeError
Enter fullscreen mode Exit fullscreen mode

This gets worse, as Seth explains in the video above, if a function is only occasionally called with different data types. The engine might guess that the data will always be strings and create a string-only version of the function, which runs very fast. When some other object is passed, the engine can’t throw an error – it’s valid code – so it has to stop and compile a less efficient, more flexible version of the function.

Our code violated the assumptions the engine made, so even if every other value ever passed is a string, the engine may not be able to switch back to a higher-performing version.

Readability

It turns out that developers can benefit from – or be tricked by – similar assumptions. When we use a function for different data types, it becomes harder to maintain code. If you forget or don’t know that getLength is also used by a custom class, you might modify it in ways that break less common uses.

Summary

A little knowledge can go a long way. Now we can apply some guidelines to our code that can make it perform better and be easier to maintain.

Writing It Right

  • When creating classes, define all properties up front.
  • Use only one data type for each property of an object.
  • Use consistent arguments and return types for functions.
  • Consider adding type annotations with JSDoc or TypeScript
  • Comments in general can be helpful.

Calculated Risks

And remember: these are recommendations, not laws. How frequently a piece of code runs may have more performance impact than the style of that code. Don’t eliminate a good solution for a single configuration object because you wouldn’t use it on a 10,000-element collection.

The Lesson

We started with the lesson!

Don't violate the assumptions of the hidden class!
— Me, just a bit ago

💖 💪 🙅 🚩
oculus42
Samuel Rouse

Posted on November 9, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related