1.1 - JS Objects


1. Object structure

A typical object in Javascript looks like in Figure 1, containing an elements and a properties array. Quite simple, elements is for indexed properties (e.g. a[1] and a[100]), whereas properties is for named properties (e.g. a.apple or a.peanut).

Figure 1. Source: Fast Properties in v8

Elements is a typical array, where an element’s index corresponds to its offset in memory. Though this is not always the case, when necessary (elements are sparse), a dictionary will be used instead.

On the other hand, properties is also an array, but requires additional metadata to deduce the location of a property. Such information is contained in a HiddenClass. The HiddenClass stores information about the “shape” of an object, and a mapping from property names to index in the properties array. Similar to elements, there are also cases where dictionary will be used instead of an array.

2. Properties

2.1 HiddenClasses and DescriptorArrays

Earlier, it was mentioned that HiddenClasses are used to store information about properties, such as property names and how to locate the property in memory.

Figure 2. Source: Fast Properties in v8

As seen in Figure 2, the first field of a JS object points to a HiddenClass. In the hidden class some metadata are stored, most notable is the third bit field, which stores the number of properties.

Aside from that, the DescriptorArray would provide the greatest interest. It contains information about named properties like the name itself and the position where the value is stored.

Obviously it would not be most efficient to create a new HiddenClass with the same properties when a new property is added to an object. v8 handles this situation by using “transitions”, i.e. chain of HiddenClasses (Figure 3). When a new property is added to an object, a new HiddenClass is created but it only contains the new property. To retrieve information about the other properties, there is a link to the old HiddenClass, such link is called a transition.

Figure 3. Source: Fast Properties in v8

2.2 Types of properties

Knowing the data structures involved in storing named properties, 3 different types of properties can be discussed.

2.2.1 In-object property

Despite having a properties array, when there are very few properties (only 1 or 2), they are stored directly inside the object (refer to Figure 4), instead of the properties array. In this case, there is minimal indirection and hence the fastest.

2.2.2 Normal property

Otherwise under a normal situation, properties will be stored in the properties store as shown in Figure 4.

Both in-object properties and normal properties are called fast properties, as they are relatively fast compared to slow properties described in 2.2.3.

Figure 4. Source: Fast Properties in v8

2.2.3 Slow property

If many properties get added and deleted from an object, it can generate a lot of time and memory overhead to maintain the descriptor array and HiddenClasses. Hence, V8 also supports so-called slow properties. An object with slow properties has a self-contained dictionary as a properties store instead of an array (Figure 5).

In this case, the HiddenClass and DescriptorArray are no longer used to maintain metadata about the properties as they are all stored in the dictionary. Such implementation does not support the usage of inline caches, hence it is very slow as compared to the aforementioned fast properties.

Figure 5. Source: Fast Properties in v8

3. Elements

Handling of integer-index properties is actually way more complex than it seems, as it turns out there are 20 different kinds of elements.

3.1 Packed or Holey Elements

Sometimes in Javascript there will be “holes” in an array (e.g. [1,,3] or when an element is removed from the array). In this case, the JS engine will have to traverse the prototype chain to find out the value at this index (Figure 6), which affects performance. In memory, a hole is represented with a special constant called the _hole.

Figure 6. Source: Fast Properties in v8

3.2 Fast or Dictionary Elements

As a consequence, when the elements store is too sparse/holey, or when the indices are too large, v8 will switch to use a dictionary instead. Clearly it does not make sense to store 10000 holes.

3.3 Smi and Double Elements

For fast elements there is another important distinction made in V8. For instance if there are only integers in an array, the GC does not have to look at the array, as integers are directly encoded as so called small integers (Smis) in place.

Another special case are arrays that only contain doubles. Unlike Smis, floating point numbers are usually represented as full objects occupying several words. However, V8 stores raw doubles for pure double arrays to avoid memory and performance overhead. Listing 1 shows 4 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 prototype

Listing 1. Source: Fast Properties in v8

3.4 ElementsAccessor (Fun fact)

As mentioned earlier, there are 20 different types of elements in v8. How this is handled under the hood is quite interesting, with the use of CTRP, specialized versions of Array functions are created for the ElementsAccessor, instead of implementing the same function repeatedly.

Whenever an Array function is called, v8 dispatches through the ElementsAccessor to the specialized version of the function.

Additional Reading