As of October 1, 2023, LINE has been rebranded as LY Corporation. Visit the new blog of LY Corporation here: LY Corporation Tech Blog

Blog


V8 Hidden class

As a frontend engineer, I feel I still need to know more about the structure of JavaScript and web browsers, in order to provide quality UX to our users. Thankfully, we are given an access to peak through JavaScript's thoughts, thanks to Google and Mozilla for making their engine code available to public, along with nicely prepared documentation. The availability of various means for tracing and profiling code surely helps too.

I'd like to take this opportunity to take you through hidden class, chosen by V8, Chrome's JavaScript engine for optimization.

This is a translated copy of the original post in Japanese, V8のHidden Classの話.

Restriction of using dictionary-like structures in dynamic typing

As we all very much know, JavaScript is a dynamic language, or as some describe, it's dynamically typed. In other words, data type is determined at the time of execution, making accessing object properties fairly slow, in comparison to statically typed languages. Theoretically, unless you use dynamic data type—for example, variable-length array—with statically typed language, you can determine memory offset of object properties or structure members, at the time of compiling. Theoretically speaking, that is. In other words, memory offsets are stored at the time of property declarations, and the memory offset values stored can be used when we need to fetch the properties values.

Contrarily, determining property's memory offset at the time of compiling is impossible with dynamically typed language. Because, the actual data type or the order of data at declaration is likely to be changed when the data is accessed. Which invalidates offsets made at declaration. Unless we come up with a solution, we need to look up a property every time we need to access it. In a fancy word, we are faced with dynamic lookup. Especially if we use dictionary-like objects as in JavaScript, we surely have to deal with the cost for reading object properties, and the cost will depend on how you write your code.

How does V8 go around dynamic lookup?

V8 gets away from dynamic lookup by employing hidden classes. How it works is like this. Whenever a property has its value changed, we update the offset of the property and keep the value.

These are the characteristics of hidden classes:

  • Every object has a hidden class of its own.
  • A hidden class contains memory offset for each property.
  • When a property is created dynamically, or a property is deleted or changed, another hidden class is created. The new hidden class keeps the information on the existing properties and at the same time, keeps the memory offset of the new property.
  • A hidden class knows which hidden class to refer to when a property is changed.
  • If an object gets a new property, the transition information of the object's hidden class is checked. If the transition information contains the condition identical to the property change, then the object changes its hidden class to the one defined in the transition information.

Let's have a look at how a hidden class gets created. When an object is created, a hidden class gets created; it's a must. For instance, when the following code is ran, we get ourselves a hidden class, the obj object gets hidden class and is linked to it. Let's call this hidden class, C0. At this point, the hidden class contains no information in it. Why? Because obj has no properties at this point.

var obj = {};

After creating an object, assign a value to the obj.x property. This is when another hidden class, C1, gets created containing a memory offset of x. The obj object originally was linked to C0, but now with the new property, x, it now looks at the class C1. And this is the interesting part. A condition, which we call transition will be added in the C0 class, stating that 'If you add a property x, then the hidden class for obj will be changed to the C1 class'.

var obj = {};
obj.x = 1;

Next, we add another property, obj.y and assign a value to it. Again, another hidden class, C2, gets created and the obj object will change its reference to the new one, the class C2. The C1 class will not get a memory offset added for the y property. Instead, a hidden class is created and the object changes its hidden class reference to the new one. Similar to the class C0, the class C1 will have a piece of information added to it, stating that if a property, y, is added, then the obj object will change its hidden class reference to C2.

var obj = {};
obj.x = 1;
obj.y = 1;

After getting all these linkages in between hidden classes, suppose we want to access the obj.y property. We will first check which class is the hidden class for the obj object. What do you think? With our example, the hidden class will be the C2 class. And with that, we will access the y property based on the memory offset for ywhich is contained in the class C2. This is how V8 avoids dynamic lookup.

What is hidden class transition?

Previously, I've mentioned that a hidden class gets to have 'transition'. Let's find more about it with the following code example.

function Person(name) {
  this.name = name;
}

var foo = new Person("yonehara");
var bar = new Person("suzuki");
console.log(bar.name);

When var foo = new Person("yonehara"); is executed, we will have the following information, and the foo object will be looking at the class C1.

  • Hidden Class C0
    • No property offset values
    • Transition information stating that 'If name gets added then the hidden class should be changed to the class C1'.
  • Hidden Class C1
    • Memory offset value of the name property.

Next we have another object, bar, created, and it references the hidden class C0. As we all know, the class C0 already has transition information that 'If name is added to the object, the hidden class will be changed to the C1 class'. So, when the bar object adds the property name to it, no hidden class will be created. Instead, the bar object will simply change its hidden class refrence to the class C1. This design eliminates the possibility of having too many hidden classes while accomplishing effective memory offset management.

Checking whether two objects reference the same hidden class, on Chrome

There is a way to check if objects reference the same hidden class or not. We could use d8, a debugger and shell for V8, but we can simply use Chrome DevTools.

First launch Chrome and follow the steps below:

  1. Open up DevTools on Chrome.
  2. Run the following code on the Console.
    function Person(name) {  this.name = name;}

    var foo = new Person("yonehara");var bar = new Person("suzuki");

  3. Take a snapshot.
  4. Search for 'Person' from the Memory tab.

After running the code, the memory status will be shown as below. As you can see from the snapshot, the two Person objects have the same ID for the map field. Hidden classes are called Maps on code. The two Map IDs being the same indicates that each object is referring to the same hidden class.

Now, let's add a property, job in the foo object. Theoretically the foo object references a new hidden class, and the previous hidden class references the new hidden class.

function Person(name) {
  this.name = name;
}

var foo = new Person("yonehara");
var bar = new Person("suzuki");
foo.job = "frontend";

Let's take a snapshot once again. Can you see the difference? The map ID of the two Personobjects are different now. In other words, the hidden class of the foo object and that of the bar object are different. Can you see the transition field under map? We don't know the conditions, but we can tell that the hidden class (ID: Map @242289) of bar internally references the hidden class (ID: Map @242289) for foo.

Optimization based on Hidden Classes

So, we've learnt what it's all about. Hidden classes help you with accessing data. On top of hidden classes, we have another method for optimization called inline caching. Inline caching is not just for V8, it's been around for a while, for caching values or function results. Having hidden classes doesn't allow us to avoid dynamic lookup unless we make use of it. We use offset values contained in hidden classes to access values, and there can be times when we make access the same value repeatedly. In such case, using cached value will be quite helpful, and this practice is called inline caching.

I want to go over inline caching as well... but I am running out of time now. Hopefully I'll bring you some more on it later.