Firing on all cylinders(Part 1): Understanding object value lookup in JavaScript interpreters and the rise of hidden classes

There is a lot of information around writing performant JavaScript and optimizing your code for the v8 engine. When you are reading through this information you will see a lot of phrases like inline caching, hidden classes, and memory offset; but what …


This content originally appeared on DEV Community and was authored by Austin Burger

There is a lot of information around writing performant JavaScript and optimizing your code for the v8 engine. When you are reading through this information you will see a lot of phrases like inline caching, hidden classes, and memory offset; but what does it all mean? You'll come across quick one-liners about "always instantiating your object properties in the same order" or, better yet, "assign all of the objects properties in the constructor". You try to dive into the documentation only to see branching charts with a million tiny words in it that look more like Harry Potter's family tree. At the end of all this, you end up just trying to commit those one liners to memory without fully understanding why.

In this series, I am going to attempt to explain these concepts in a way where we don't need pages of flow charts, and with relatable examples (not just look at this code).

What to expect

We will start by going over the difference between dynamic and non-dynamic languages (mostly pertaining to how they store objects in memory). Then, in part 2, we will dive into the v8 engine and the methods it uses to efficiently handle the concepts we discuss in part 1. Also, in part 2, I will describe the common pitfalls and ways you can increase the performance of your code. However, you can make the code better without first understanding the why.

Before we begin

Although I am going to try and explain these concepts in an approachable way, these are not easy concepts to grasp. Most developers can go their entire career without digging into the minute details of how a particular JavaScript engine accesses objects in memory.

Modern JavaScript Interpreters, like v8, are amazing tech and mostly handle all of this for you. Furthermore, with TypeScript, you have a compiler that can help keep you from making a lot of the common mistakes that can lead to a decrease in performance. However, taking the time to try and understand what is happening under the hood can go a long way.

The dynamic language

JavaScript is a dynamic programming language. This means that you can add, remove, and change (the type) property values of objects after they are initialized. Let's look at the following code:

// Define a simple constructor function for an employee
const employee = function(salary, position) {
  this.salary = salary;
  this.position = position;
};

// Instantiate a new employee
const newHire = new employee(50000, 'sales');

// Dynamically add the employee's desired pay day
newHire.payDay = 'Saturday';

After the employee object is created, their preferred payDay is added dynamically. This is all perfectly valid JavaScript. It will run just fine and the newly hired employee will get paid every Saturday.

The difference between a non-dynamic programming language (meaning all of an objects properties are fixed before compilation) is that new properties cannot be added or removed at runtime. The benefit to a language being non-dynamic is that the values of these properties (or pointers) can be stored in memory with a fixed offset (an integer indicating the displacement between the beginning of an object in memory and any given property). This offset can be easily determined based on the properties type.

Skrrtttt... offset?!?! displacement?!?! You said this would be easy to follow and relatable!

You're right, this is why I decided to do this blog in two parts.

Memory storage displacement (offset)

The easiest way to explain this is with a simple data structure like an array:

const array = ['value1', 'value2', 'value3', 'value4'];

We know we can access a value in that array using its index:

array[2]; // this will get us the item at index 2 ('value3')

If the first value ('value1') is at memory position 0, moving two places to the right will give you ('value3'). So 'value3' has an offset of 2 from the start of where the array is stored in memory.

This is simple enough for an array, however not all objects are stored in memory sequentially like an array is. With more complex objects, like the employee function above, you can't be sure where the object, and its properties, will be stored. Thus making it harder to determine the offset between the objects 'shell' (to keep it simple) and its properties. You could have the 'shell' of the object (function employee() {}) at position 0, then its property this.salary at position 6 with other objects in between.

Back to dynamic vs. non-dynamic

In order to keep up with these offset values, non-dynamic languages (i'll use Java in this case) create a fixed object layout. This layout (or mapping) cannot be changed (as in changing the type), added too, or removed from at runtime. The offset is written in stone, making it easy (usually one instruction) to grab any property value of a given object.

Since you can add, remove, and even change a properties type in JavaScript at runtime, the offset values cannot be set in stone. Instead of using a fixed object layout, like in the non-dynamic Java, most JavaScript interpreters use a dictionary like structure (based on a hash function) to store the objects property values in memory. Because of this, grabbing the properties value from a hash table is more computationally expensive (more instructions) than the fixed object layout of a non-dynamic language.

I won't go more into hash tables, but you can start here. Just know it is extremely inefficient.

Firing on all cylinders

Since using hash tables to get property values is so inefficient, the JavaScript engine NodeJS uses, 'v8', takes a different approach. This approach is built around using Hidden Classes and made faster by Inline Caching.

In part 2 of this series we will dive into hidden classes and inline caching. Once you better understand the concepts, we can get into techniques you can use to make sure your JavaScript is as performant as it can be.

Further reading

How Java stores objects in memory

The official v8 engine blog

MDN JavaScript types and data structures

Wikipedia article on Offset


This content originally appeared on DEV Community and was authored by Austin Burger


Print Share Comment Cite Upload Translate Updates
APA

Austin Burger | Sciencx (2022-01-16T23:23:07+00:00) Firing on all cylinders(Part 1): Understanding object value lookup in JavaScript interpreters and the rise of hidden classes. Retrieved from https://www.scien.cx/2022/01/16/firing-on-all-cylinderspart-1-understanding-object-value-lookup-in-javascript-interpreters-and-the-rise-of-hidden-classes/

MLA
" » Firing on all cylinders(Part 1): Understanding object value lookup in JavaScript interpreters and the rise of hidden classes." Austin Burger | Sciencx - Sunday January 16, 2022, https://www.scien.cx/2022/01/16/firing-on-all-cylinderspart-1-understanding-object-value-lookup-in-javascript-interpreters-and-the-rise-of-hidden-classes/
HARVARD
Austin Burger | Sciencx Sunday January 16, 2022 » Firing on all cylinders(Part 1): Understanding object value lookup in JavaScript interpreters and the rise of hidden classes., viewed ,<https://www.scien.cx/2022/01/16/firing-on-all-cylinderspart-1-understanding-object-value-lookup-in-javascript-interpreters-and-the-rise-of-hidden-classes/>
VANCOUVER
Austin Burger | Sciencx - » Firing on all cylinders(Part 1): Understanding object value lookup in JavaScript interpreters and the rise of hidden classes. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2022/01/16/firing-on-all-cylinderspart-1-understanding-object-value-lookup-in-javascript-interpreters-and-the-rise-of-hidden-classes/
CHICAGO
" » Firing on all cylinders(Part 1): Understanding object value lookup in JavaScript interpreters and the rise of hidden classes." Austin Burger | Sciencx - Accessed . https://www.scien.cx/2022/01/16/firing-on-all-cylinderspart-1-understanding-object-value-lookup-in-javascript-interpreters-and-the-rise-of-hidden-classes/
IEEE
" » Firing on all cylinders(Part 1): Understanding object value lookup in JavaScript interpreters and the rise of hidden classes." Austin Burger | Sciencx [Online]. Available: https://www.scien.cx/2022/01/16/firing-on-all-cylinderspart-1-understanding-object-value-lookup-in-javascript-interpreters-and-the-rise-of-hidden-classes/. [Accessed: ]
rf:citation
» Firing on all cylinders(Part 1): Understanding object value lookup in JavaScript interpreters and the rise of hidden classes | Austin Burger | Sciencx | https://www.scien.cx/2022/01/16/firing-on-all-cylinderspart-1-understanding-object-value-lookup-in-javascript-interpreters-and-the-rise-of-hidden-classes/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.