Translated from richardartoul. Making. IO/jekyll/upda…

Hidden Classes

As we all know, Javascript is a dynamic programming language, which means that objects can add and delete properties even after they are initialized. For example, in this code, the “car” object is initialized with “make” and “model” attributes, but then the “year” attribute is dynamically added to the object.

function Car(make,model) {
	this.make = make;
	this.model = model;
}

const car = new Car(honda,accord);

car.year = 2005;
Copy the code

Most Javascript interpreters use the class dictionary structure to store the in-memory location of a class’s attributes. However, this structure makes Javascript more performance-intensive than a static language like Java when it comes to look-up property values. In Java, all object properties are determined by a fixed object structure before compilation, and cannot be added or deleted dynamically at runtime. The advantage of this is that the values of attributes (or Pointers to attributes) can be stored in a contiguity of memory at fixed offsets from each other. An attribute’s offset can be easily determined by its type, but this is not possible in Javascript because the attribute type can be dynamically changed at run time.

In a non-dynamic language like Java, a single instruction can determine the memory location of an attribute, but in Javascript multiple instructions are required to look up the memory location of an attribute in a hash table. This results in property look-up in Javascript being much slower than in other languages.

Given that dictionary tables are such an inefficient way of looking up the memory location of attributes, V8 takes a radically different approach and hides classes. In fact, despite the run-time differences between hidden classes, it is very similar to the fixed object structure in Java. Before you read the following, make two important points: first, V8 associates each object with a hidden class, and second, the purpose of a hidden class is to optimize the speed of access to attributes. Let’s get down to business.

function Point(x,y) {
	this.x = x;
	this.y = y;
}

const obj = new Point(1.2);
Copy the code

Once a new method is declared, Javascript creates a hidden class, C0.

No properties have been declared at this point, so C0 is now null.

Once the first statement “this.x=x” is executed, V8 will create a second hidden class C1 based on C0. C1 records where attribute X can be found in memory. In this example, x is stored at offset 0, which means that an in-memory object target can be considered as a contiguous space. And the first offset in this space represents the property X. At the same time, V8 will update C0 with a “class offset” operation, which indicates that the attribute X has been added to the target object. After that, the hidden class pointer to the target object will point to C1.

Whenever the target object adds a new property, the object’s old hidden class changes path to a new hidden class. The important thing about hidden classes is that they can be shared by objects created through the same creation process. If two objects share a hidden class and add the same attributes to both objects, the transformation guarantees the same hidden class, and the code is optimized.

This is repeated when “this.y=y” is executed. A new hidden class called C2 will be created, then C1 will be transformed to indicate that the property Y has been added to the target object, and finally the hidden class will point to C2. This updates the hidden class of the target object to C2.

Note: The transformation of the hidden class depends on the order in which attributes are added to the target object. Notice the following code:

1  function Point(x,y) {
2    this.x = x;
3    this.y = y;
4  }
5 
7  var obj1 = new Point(1.2);
8  var obj2 = new Point(3.4);
9
10 obj1.a = 5;
11 obj1.b = 10;
12
13 obj2.b = 10;
14 obj2.a = 5;
Copy the code

Obj1 and obj2 share the same hidden class up to act 9. However, when attributes A and B are added to the two objects in reverse order, this results in the last two objects being transformed in different paths to produce two different hidden classes.

At this point, some readers may think that having two different hidden classes for two objects is not a serious problem. As long as the correct offset is stored in the hidden class, accessing attributes should be just as fast as sharing the same hidden class. To understand why this is wrong, it’s worth introducing another V8 optimization technique, inline caching.

Inline Caching

V8 leverages another technique to optimize the performance of dynamically typed languages, called inline caching. If you want to learn more about in-line caching, you can refer to this, but simply put, in-line caching relies on the observation that repeated calls to methods have a high probability of using the same type of arguments.

So how does it work? V8 will maintain a cache of parameter types passed in during recent calls to the method, and then use that information to predict which parameter types will be passed in future calls. Once the V8 engine has correctly predicted the type of the parameter, it will allow the engine to bypass the process of parsing how to access the class attributes and use previously cached information to retrieve the hidden class and access the object attributes directly.

So why are the concepts of hidden classes and inline caching so closely related? Whenever a method is called on a particular object, the V8 engine looks for the object’s hidden class to obtain offsets for subsequent access to that particular property. After twice successfully calling the same method with the same hidden class parameter, the V8 engine skips the hidden class lookup and simply adds the offset of the property. For subsequent calls, the engine will assume that the hidden class will remain unchanged and will directly access memory using the offset cached from the previous lookup, which will greatly improve access speed.

Inline caching is an important reason why objects share hidden classes. If you create two objects of the same type but with different hidden classes, the engine will not be able to optimize the inline cache because different hidden classes represent different attribute offsets.

Optimization related suggestions

  1. Ensure that object attributes are instantiated in the same order, which ensures that they share the same hidden class.
  2. Adding attributes to an object after it has been instantiated will force hidden class changes, which will slow access to methods that have also been inline cached. So try to make sure that all property declarations are made inside the constructor.
  3. Code that executes the same method over and over will be faster than code that executes different methods all the time.