What is V8?

V8 is simply a JavaScript engine. We can think of a JavaScript engine as a translator that translates JavaScript, a programming language that humans can understand, into a computer language that computers can understand. Before V8, all JavaScript engines were interpretive, which was a major reason for JavaScript’s slow execution. V8 was the first to introduce just-in-time (JIT) dual-wheel drive design, a tradeoff strategy in which a mix of compile execution and interpreted execution resulted in a significant increase in JavaScript execution speed. Since V8 came along, the major vendors have introduced JIT in their JavaScript engines, so you’ll see similar architectures in JavaScript engines today. V8 also introduced lazy compilation, inline caching, and hidden classes earlier than other engines, further optimizing the efficiency of compiling and executing JavaScript code. For all its virtues, I’m sure that for most of you, V8 is still a black box. We throw a piece of code into that black box and it returns results without much insight into how it works.

Why study V8

If you simply use JavaScript and call Web apis without understanding the inner workings of the engine, many of the problems you encounter on a project may not be solved. For example, sometimes projects take up too much memory, or pages respond too slowly, or tasks get blocked while using Node.js, all due to V8’s basic operating mechanism. If you’re familiar with how V8 works, there’s a systematic way to solve these problems.

JS object

You all know that an object in JavaScript is made up of a set of key-value pairs, like a dictionary, with strings as key names and arbitrary data types as key values that can be read or modified by key names. However, when implementing object storage in V8, for the sake of performance, it does not adopt a single dictionary storage mode. Because dictionaries are nonlinear data structures, the query efficiency will be lower than linear data structures. V8 adopts a set of complex storage strategies to improve storage and search efficiency.

General properties and Sort properties

In V8, objects are made up of three Pointers: Hidden Class, general Properties, and sorted Element.

Today we are going to talk about general and sort properties. You can see the following code:

function Score() {
    this["B"] = 'Str-B'
    this[50] = 'test-50'
    this[100] = 'test-100'
    this[9] = 'test-9'
    this[8] = 'test-8'
    this["A"] = 'Str-A'
    this[1] = 'test-1'
    this[3] = 'test-3'
    this[5] = 'test-5'
    this["C"] = 'Str-C'
}
var obj = new Score()
for (key in obj) {
    console.log(`index:${key}  value:${obj[key]}`)}Copy the code

We create an obj object using the Score constructor. In the Score constructor, we set a number of properties to the obj object, including the number and string properties. Then we print all the properties of the obj object by enumeration.

Observe the data we can see, print out the properties of the sequence is not we set order, when we set object is out-of-order Settings, such as to set up the first string B, 50, and then set up a relatively large Numbers and the number 1 is set up in the later, but the printing result is very regular, generally reflected in the following two points:

  1. The numeric properties set are printed first and are sorted by the size of the number.
  2. The string attributes will be printed in the same order as they were set. For example, if we set B-A-C first, we will print them in the same order.

This result occurs because the ECMAScript specification defines that numeric attributes should be sorted in ascending order by index value size, and string attributes in ascending order by order of creation.

Here we call the numeric properties in the object sort properties, which are called Elements in V8, and the string properties are called general properties, which are called properties in V8.

In V8, in order to effectively improve the performance of storing and accessing these two attributes, two linear data structures are used to store sorted attributes and general attributes respectively. The specific structure is shown as follows:

As you can see from the figure above, the obj object contains two hidden properties: The elements property refers to the Elements object, where the sort properties are stored in order, and the properties property refers to the properties object, In the Properties object, the general properties are saved in the order they were created.

After splitting the two linear data structures, if an index is performed, V8 completes an index by reading all elements sequentially from the Elements property and then from the properties property.

Fast properties and slow properties

V8 simplifies the program by storing different attributes in elements and properties, but it adds an extra step to finding elements, such as the obj.B statement, to find the value of B’s attributes. In V8, the object properties pointed to by the properties property will be found first, and then the property B will be found in the properties object. This method adds an extra step in the search process, thus affecting the efficiency of the element search.

For this reason, V8 adopts a tradeoff strategy to speed up the efficiency of finding properties by storing some general properties directly into the object itself, which we call in-object properties. You can see how objects are represented in memory:

After using in-object attributes, the general attributes are saved to the OBJ object itself, so that when using obj.B to find the value of B’s attributes again, V8 can directly obtain the value from the OBJ object itself. This method reduces the steps to find the value of the attributes, and increases the search efficiency.

However, the number of attributes in an object is fixed, 10 by default, and if you add attributes that exceed the space allocated by the object, they are stored in the regular property store. Although the property store has an additional layer of indirection, it can be expanded freely.

Normally, we will be saved in the properties of linear data structure called “properties”, because only need by index in linear data structure that can access to the property, although the access speed of linear structure, but if you add or remove from the linear structure when a large number of attributes, the execution efficiency is very low, this is mainly because will produce a lot of time and memory overhead.

Therefore, if an object has too many attributes, V8 adopts another storage strategy, the “slow attribute” strategy, but the slow attribute object has a separate nonlinear data structure inside it as an attribute storage container. All attribute meta-information is no longer stored linearly, but is stored directly in the attribute dictionary.

Let’s put this into practice: View object layouts in Chrome

Now that we know how V8 stores objects, let’s take a look at how objects are laid out in memory in conjunction with Chrome’s memory snapshot. You can open Chrome Developer Tools, select the console TAB, and then execute the following code in the console to view the memory snapshot:

Elements = Elements; Elements = Elements;

function Foo() {}
var bar = new Foo()
for (let i = 0; i < 10; i++) {
    bar[i] = 'bar' + i
}
// bar[1111] = 'bar1111'
Copy the code

Above we created a constructor and used it to create a new bar object. Then I added 10 sort properties to the bar object through enumeration. Let’s see how it is stored in the object.

Now that the function object is created, let’s look at the state of the constructor and object in memory. To find the object we just created, you can type the constructor Foo into the search box. Chrome lists all the objects created by the constructor Foo, and the final screenshot looks like this:

Looking at the image below, we search for all the objects created by the constructor Foo. When we open the drop-down list of Foo, the first one is the bar object we just created. We can see that the bar object has a elements property, which contains all the sorting properties we created. You can see that its data structure is linear

Bar [1111] = ‘bar1111’

As you can see, the elements property is stored out of order because when we add bar[1111], the array becomes sparse. To save space, sparse arrays are converted to hash storage rather than a full array describing the storage space. Therefore, these indexable attributes can no longer calculate the memory offset directly from their index value, which is the slow attribute strategy.

Let’s look at the storage rules of the Properties property:

function Foo2() {}

var a = new Foo2()
var b = new Foo2()
var c = new Foo2()

for (var i = 0; i < 10; i ++) {
  a[new Array(i+2).join('a')] = 'aaa' + i
}

for (var i = 0; i < 12; i ++) {
  b[new Array(i+2).join('b')] = 'bbb' + i
}

for (var i = 0; i < 30; i ++) {
  c[new Array(i+2).join('c')] = 'ccc' + i
}
Copy the code

Above we created a constructor and used it to create three new objects. Then I added 10, 12, and 30 general attributes to each object through enumeration. Let’s see how they are stored in the object.

By looking at the figure above, we can see that the first A object, when it has only 10 general properties, stores all the general properties directly in the A object as internal properties.

When there are more than 10 general attributes in the B object, the first 10 general attributes are directly stored in the B object in the form of internal attributes, and the rest are added to the properties property and stored in the form of linear data structure.

When the normal properties exceed a certain amount (30 in this experiment), you can see that the indexes in the properties properties become random numbers, indicating that the object has become a hash access structure, which is the slow attribute strategy.

Do you see how V8 speeds up access to object properties?

The results of our practice confirm what we have described above:

  1. V8 started with a fast attribute storage strategy: the sort attribute Element and the general attribute Properties. The linear data structure is used to store the properties of objects, and when the general properties are less than 10, they are promoted to internal properties to improve the access speed and stored directly in the object itself.
  2. However, when the index of the sorted attribute element is unordered or a sparse array and the conventional attribute properties exceeds a certain number, another storage strategy is adopted: slow attribute, which has an independent nonlinear data structure inside as an attribute storage container.

conclusion

This time we looked at how objects are stored internally in V8. Because objects in JavaScript are made up of groups of attributes and values, the easiest way to store attributes and values is to use a dictionary, which is much less efficient because of its non-linear structure. To improve lookup efficiency, V8 adds two hidden attributes to the object, the sort attribute Element and the regular attribute Properties. The Element attribute points to elements, where the sort attributes are stored in order. The Properties property points to the Properties object, where the general properties are saved in the order they were created. The introduction of these two attributes speeds up V8’s ability to find attributes. To further improve efficiency, V8 also implements a strategy of built-in attributes. When there are fewer than 10 general attributes, V8 writes them directly into objects, saving another intermediate step. There are too much, but if the attributes of objects or repeatedly to add or delete attributes of operation, the V8 will downgrade the linear storage mode to the nonlinear model of storage for more than a certain value, the sequential search is not fast enough, need to pass a hash table structure search, speed, also promoted to modify the speed of the object’s properties.

Here’s a comment from a big shot:

1, Chrome display

Don’t care if the element or property properties are displayed in the primary directory, chrome should display them no matter how they are stored for debugging purposes. It is more accurate to look directly at the contents stored in Elements and Properties. If there are no properties, there are no more than 10 propertie properties.

2, the element

Element is not built in. By default, Element should have a contiguous storage structure that wastes space in exchange for time and allows direct subscript access to speed up access. But when element ordinals are very discontinuous, they are optimized to hash tables because the space wasted is too large to be economical.

3, the properties

There are only ten built-in properties, but it is recommended to consider these ten separately so that they will be easier to figure out later. Properties uses a linked list structure by default. When the amount of data is very small, the search will be quick, but when the amount of data increases to a certain value, it will be optimized into a hash table. After a certain value, sequential lookup is not fast enough, and you need to use the hash table structure to improve the speed.

4. Shouldn’t the hash table be looked up once? Why slow query

To resolve key conflicts, a hash table usually uses a list to store multiple conflicting keys. Therefore, sequential access is required after hash calculation, so multiple accesses are required. In addition, it involves hash scaling, which is even slower. So, overall, hash is slower than access by address; When the data volume is small, it is slower than the sequential access of the linked list.

5. How does the hash table store the property order?

Using a linked list to record the insert properties, similar to the LinkedHashMap in Java, solves the problem

This article is a summary of learning, if there are mistakes welcome to point out, thank you

References:

  • Time.geekbang.org/column/arti… Geek time teacher Li Bing’s illustration of Google V8
  • www.cnblogs.com/chargeworld… Object representation in V8