• Fast Properties in V8
  • Originally written by Camillo Bruni
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: Cherry
  • Proofreader: Dearpork, Schrodinger’s cat

How does the V8 engine access attributes quickly

In this article I’ll explain how JavaScript attributes are handled internally in the V8 engine. From a JavaScript perspective, properties aren’t that different; JavaScript objects behave more like dictionaries, with strings as keys and arbitrary objects as values. In the ECMAScript language specification, there is no clear distinction between numeric indexes of objects and other types of indexes, but this is not the case within the V8 engine. Otherwise, different attributes behave the same regardless of whether they can be indexed by integers.

While the different representations of attributes in V8 do have an impact on performance and memory, in this article we will explain how the V8 engine enables fast attribute access when dynamically adding attributes, understand how attributes work, and explain how the V8 engine is optimized (such as inline caching).

This article explains the difference between handling integer indexed attributes and named attributes, and then we show how V8 uses HiddenClasses to add a named attribute in order to provide a quick way to define an object’s model. Then, we’ll take a closer look at how to optimize the naming of attribute names based on usage for quick access or quick modification. In the final section, we describe how V8 handles the details of integer index attributes or array indexes.

Name attributes and elements

Let’s start by examining a very simple object, such as {a: “foo”, b: “bar”}. This object has two named attributes, “a” and “b”. It does not use any integer indexes as attribute names. We can also use indexes to access properties, especially if the objects are arrays. For example, the array [“foo”, “bar”] has two properties that can be indexed using an array: the value of index 0 is “foo”, and the value of index 1 is “bar”.

This is the first major difference in V8’s general handling of attributes.

The following figure shows what a basic JavaScript object looks like in memory.

Elements and attributes are stored in two separate data structures, which makes it more efficient to add and access attributes and elements using different schemas.

The element is used for various array. prototype methods such as POP or slice. Given that these functions access attributes in a contiguous-range storage area, they are mostly represented as simple arrays internally in the V8 engine. Later we’ll explain how to save memory by using a sparse dictionary-based representation.

The storage of named attributes is similar to the storage of sparse arrays. However, unlike elements, we can’t simply use keys to infer their position in an attribute array; we need some additional metadata. In V8, every JavaScript object has a HiddenClass associated with it. This HiddenClass stores model information for an object, among other things, there is a mapping from attribute names to attribute indexes. We sometimes use a dictionary instead of a simple array. We’ll explain this in more detail in a special section.

Key points of this section:

  • Array index properties are stored in a separate element store.
  • Named properties are stored in the properties store.
  • Elements and attributes can be arrays or dictionaries.
  • Each JavaScript object has one associated with the object’s modelHiddenClass

HiddenClasses and descriptor arrays

Having covered the general differences between elements and named attributes, we need to take a look at how HiddenClasses work in V8. HiddenClass stores metadata about an object, including the number of objects and object reference prototypes. HiddenClasses are similar in concept to “classes” in a typical object-oriented programming language. However, in prototype-based programming languages like JavaScript, it is generally impossible to know classes in advance. So in this case, in the V8 engine, HiddenClasses create and update dynamic changes to properties. HiddenClasses serve as an identification of the object model and are a very important factor in the V8 engine’s optimization compiler and inline cache. HiddenClass lets you maintain a compatible object structure so that instances can use inline properties directly.

Let’s take a look at the highlights of the HiddenClass

In V8, the first part of a JavaScript object points to a HiddenClass. (In fact, any object in V8 is in the heap and managed by the garbage collector.) In terms of attributes, the most important information is the third section, which stores the number of attributes, and a pointer to an array of descriptors. The descriptor array contains information about named properties, such as the name itself and where the value is stored. Note that we are not tracking integer index properties here, so there are no integer indexed entries in the descriptor array.

The basic assumption about HiddenClasses is that objects have the same structure, for example, the same order corresponds to the same properties, and share the same HiddenClass. We use different HiddenClass implementations when we add a property to an object. In the example below, we start with an empty object and add three named attributes.

Each time a new property is added, the object’s HiddenClass changes, and a transition tree is created behind the V8 engine to link the HiddenClass together. The V8 engine knows which HiddenClass you are adding. For example, if the attribute “a” is added to an empty object, the transformation tree will use the same HiddenClass if you add the same attributes in the same order. The following example shows that we will follow the same transformation tree even if we add simple indexed attributes between the two.

Key points of this section:

  • Objects with the same structure (the same order for the same attributes) have the same HiddenClasses.
  • By default, adding a new named attribute creates a new Hiddenclass.
  • Adding array index attributes does not create new HiddenClasses.

Three different named attributes

After outlining how the V8 engine uses HiddenClasses to track the object’s model, let’s take a look at how these properties are actually stored. As described above, there are two basic attributes: named attributes and indexed attributes. The following parts are named attributes:

A simple object such as {a: 1, b: 2} There are various representations within the V8 engine, and although JavaScript objects are more or less similar to external dictionaries, the V8 engine still tries to avoid dictionaries because they interfere with certain optimizations, such as inline caching, which we’ll explain in a separate article.

In-object properties and General properties: The V8 engine supports properties stored directly In so-called In-object properties. These are the fastest attributes available in the V8 engine because they are directly accessible. The number of in-object attributes is determined by the initial size of the object. If you add attributes to an object that exceed storage space, they are stored in the properties store. Properties store an extra layer of indirection but this is a separate area.

Fast vs. Slow: The next important difference comes from fast vs. slow. In general, we refer to properties stored in the linear property store area as fast properties. Fast properties are accessed only through the index of the property store. To get the name of the property in the actual location of the property store, we must go through the descriptor array in the HiddenClass.

However, adding or removing multiple attributes from an object can incur significant time and memory overhead in maintaining descriptor arrays and HiddenClasses. For this reason, the V8 engine also supports the so-called slow property, where an object with a slow property has a self-contained dictionary as its property store. All property metadata is no longer stored in the descriptor array of the HiddenClass but directly in the property dictionary. Therefore, attributes can be added and removed from unupdated hiddenclasses. Since inline caches do not use dictionary attributes, the latter are typically slower than the fast attributes.

Key points of this section:

  1. There are three different types of named attributes: object, fast dictionary, and slow dictionary.
  • Stored directly on the object itself in object properties and provides the fastest access.
  • Fast properties are stored in the properties store, and all metadata is stored in the descriptor array of the HiddenClass.
  • Slow attributes are stored in their own attribute dictionary, and metadata is no longer stored in a HiddenClass.
  1. The slow attribute allows efficient attribute deletion and addition, but slower access than the other two types.

Element or array index property

So far, we’ve looked at named attributes, ignoring integer index attributes commonly used in arrays. Integer indexed attributes are no easier to handle than named attributes. Although all index attributes are always stored separately in the element store, there are 20 different types of elements!

Are elements contiguous or default: The first major difference in V8 is whether elements are contiguous or default in storage. There is a default in the store if an index element is deleted, or if no index element is defined. A simple example is [1,,3], with the second position default. The following example illustrates the problem:

const o = ["a"."b"."c"]; console.log(o[1]); / / print"b". delete o[1]; Console.log (o[1]); // Delete a property.console.log (o[1]); / / print"undefined"; The second property does not exist o.__proto__ = {1:"B"}; // Define the second property console.log(o[0]) on the prototype; / / print"a". console.log(o[1]); / / print"B". console.log(o[2]); / / print the console. The log (o [3]); / / print undefinedCopy the code

In short, if the attributes don’t exist on the sink, we must continue to look up the prototype chain. If the element is self-contained and we do not store attributes about the current index in the HiddenClass, we need a special value, called the_hole, to mark the location where the attribute does not exist. The performance of this array function is critical. If we know there is no default, i.e. elements are contiguous, we can query the prototype chain for local operations without being expensive.

Fast vs. dictionary elements: The second major difference between elements is whether they are fast or dictionary mode. Fast elements are simple VM internal arrays where attribute indexes map to indexes in the element store. However, this simple representation is quite wasteful in a sparse array. In this case, we use a dictionary-based representation to save memory, at the expense of slightly slower access:

const sparseArray = [];
sparseArray[1 << 20] = "foo"; // Create an array with dictionary elements.Copy the code

In this case, it would be more wasteful to assign a full permutation of 10K. So instead V8 creates a dictionary in which we store three identical key-value descriptors. In this case, the key is 10000, the value is “string” and there is a default descriptor. Since we have no way to describe the details in the HiddenClass store, in V8 when you define an indexed attribute with a custom descriptor stored in a slow element:

const array = [];
Object.defineProperty(array, 0, {value: "fixed", configurable}); console.log(array[0]); / / print"fixed".
array[0] = "other value"; Console. log(array[0]); // Still print"fixed".Copy the code

In this example, we add a different property with false to the array. This information is stored in the descriptor section of the triplet of the slow element dictionary. Note that array functions execute much slower on slow element objects.

Small integers and double elements: There is another important distinction in V8 for fast elements. For example, if you only save arrays of integers, a common example is that GC does not accept arrays because integers are encoded directly into what are called small integers (SMIS). Another special case is arrays, which contain only doubles. Unlike SMIS, floating point numbers are usually represented as a few characters that an object occupies. However, V8 uses two rows to store pure double groups to avoid memory and performance overhead. The following example lists four examples of SMI and double elements:

const a1 = [1, 2, 3]; // Smi Packed const a2 = [1, , 3]; // Smi Holey, a2[1] reads from the prototype const b1 = [1.1, 2, 3]; // Double Packed const b2 = [1.1,, 3]; // Double Holey, b2[1] reads from the prototypeCopy the code

Special elements: So far, we’ve covered 7 of the 20 different elements. For simplicity, we have excluded the 9-bit array type, two string wrappers, and so on, two argument objects.

ElementsAccessor: as you can imagine, we don’t want to write 20 degree group functions in C++ for each element. That’s the magic of C++. Instead of implementing array functions over and over again, we create ElementsAccessor in accessing elements from the back store. ElementsAccessor relies on CRTP to create a professional version of each array function. So, if you call some methods in an array such as slice, this will be done by calling the V8 engine’s built-in C++ call, the professional version of ElementsAccessor:

Key points of this section:

  • There are quick and dictionary modes that index attributes and elements.
  • Quick attributes can be packaged and they can contain the default flags for deleted index attributes.
  • Array element types are fixed to speed up array functions and reduce GC overhead for engine optimization.

Understanding how attributes work is key to many optimizations in V8. Many of these internal decisions are invisible to JavaScript developers, but they explain why some code patterns are faster than others. Changing attributes or element types often causes V8 to create different hidden classes, hindering V8 optimization. Stay tuned for my next article on how the V8 engine VM works internally.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, React, front-end, back-end, product, design and other fields. If you want to see more high-quality translation, please continue to pay attention to the Project, official Weibo, Zhihu column.