How JavaScript works: Get the V8 Engine + 5 tips to optimize your code

Tram Ho

Source: https://blog.sessionstack.com/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code-ac089e62b12e

Notes: This article is a translation from another blog, I will remove the irrelevant advertisement so that you can focus on the specific knowledge shared in this article. If you want to read more deeply and carefully then you should read the original. Thanks ^^!

The first article in this series focused on an overview of the engine, runtime, and call stack. For this article we will dig deeper into the inside of Google’s V8 JavaScript engine. We also would like to share a few more tips to help you write better code and optimize (best practices).

General

The JavaScript engine is a software or can be called a compiler processing JavaScript code. A JavaScript engine can be built like a standard compiler or just -in-time (JIT) compiler that can compile JavaScript code into bytecode (binary code) in some form.

Below is a list of quite popular projects that are building a javaScript engine.

  • V8 : open source, developed by Google, written in C ++
  • Rhino : Managed by the Mozilla Foundation, is also an open source, written in Java.
  • SpiderMonkey : This is the first JavaScript compiler. It was previously developed by Netscape Navigator and now by Firefox.
  • JavaScriptCore : An open source introduced with the name Nitro and developed by Apple for their safari browser.
  • KJS : It was previously the KDE engine developed by Harri Porten for the Konqueror web browser project.
  • Chakra (JScript9): Internet Explorer
  • Chakra (JavaScript): Microsoft Edge
  • Nashorn : This is an open source, part of OpenJDK written by Oracle Java Languages ​​and Tool Group.
  • JerryScript : A lightweight compiler for the Internet of Things.

Why create V8 Engine

V8 Engine is an open source program developed by google, written in C ++. Engine used by Google Chrome. Unlike most other engines. V8 is also used in Nodejs

V8 was the first model created to increase the performance of javaScript in web browsers. To achieve optimum speed, the V8 engine compiles javaSript code into a more efficient code instead of using an interpreter. It will compile javaScript into machine language when executed by building a JIT (Just-In-Time) compiler similar to many other famous engines that have done like SpiderMonkey or Rhino (Mozilla). The main difference here is that V8 does not output bytecode or any intermediate code.

V8 used to have 2 compilers.

Before version 5.9 V8 was released as an engine with 2 compilers:

  • full-codegen — a simple compiler that generates very fast, simple machine code, but the computer code will read relatively slowly.
  • Crankshaft — a more complex optimization compiler (Just-In-Time) that generates highly optimized code.

Inside V8 also use some of the threads (threads):

  • The main thread will do what you require: fetch code, compile the code, and execute the code
  • It also has a separate thread that performs compilation. The main thread can then continue executing even while the previous thread is being optimized.
  • There is another thread called the Profiler thread that tells the runtime which method we have to spend time running for, while Crankshaft will optimize that method.
  • A few other threads will handle the Garbage Collector cleanup.

When starting to execute the JavaScript code. The fastest compiler like a full-codegen lever will immediately compile javaScript syntax directly into the machine language without any intermediate steps. This makes booting of machine language execution faster. Note that V8 will no longer use bytecode (binary) as an intermediary, so it doesn’t need an intermediate compiler.

When the code you are executed is a period of time. At that time, the Profiler thread will gather the amount of data needed to notify the system which part needs to be optimized.

At that time, Crankshaft’s optimization work will immediately start in another thread. It compiles javaScript code into a high-level static single-assignment (SSA) code called Hydrogen and it continues to try to optimize Hydrogen graph. Almost the whole process of optimizing machine code will be done in this thread.

Inlining (Inline)

(Everyone should read through the internal snow or inline function is what I have inserted the wiki link above, this is a technique in C ++)

The first thing in the code optimization process is to do as much inline as possible. Inlining is a process of replacing a piece of code (the code right where the function is called) with the body of the called function. (It’s a little confusing, so the more you read about it, the easier it will be.) This simple step allows the optimal parts to then make more sense.

Class hidden

JavaScript is a prototype-based language (translation is prototype-based): It has no classes and objects created by a copy process. JavaScript is also a very flexible language. After an object is created, its properties will be able to be added or removed easily.

Most JavaScript compilers use dictionary-like structures (based on hash functions) to store the location of the attribute values ​​of an object in memory. This structure makes accessing a property’s value in JavaScript more computationally expensive than non-dynamic programming languages ​​like Java or C #. In Java, all properties of an object are defined by a fixed layout of that object before compiling and cannot be automatically added or deleted at runtime (Uhm!, Dynamic type in C # is another topic). ). As a result, the values ​​of those properties (or pointers to it) can be stored as continuous buffers in memory with a fixed buffer between each property. The length of that fixed buffer can easily be determined based on the property’s prototype, but for javaScript, the properties of the properties change constantly at runtime, such measurements are not possible.

Because the use of dictionary to find the location of the object’s properties in memory is very inefficient, V8 uses an alternative method: Hidden Class . Hidden Class behaves just like objects with fixed layout (classes) like in Java except that it is created at runtime. And now let’s see what it looks like:

When “new Point (1, 2)” is created new. V8 will create a hidden class called “C0”

At that time, Object Point didn’t have any properties yet so “C0” would be empty then.

When the first command “this.x = x” is executed (inside funciton Point). V8 will continue to create a second hidden class called “C1” inheriting “C0”. C1 will now indicate the position in the memory of the variable x (related to the cursor). Then the variable x will be stored at offset 0. That is when we look at a point of the object in memory as a continuous buffer. Then the first buffer (offset first) will correspond to the variable “x”. At that time V8 will add “C0” a “Class transition”, it will check the state if the variable “x” has been added to the Object Point then the hidden class will then change from “C0” to “C1”. Now the Point object will have a hidden class of “C1”;

Every time a property is added to an object, the old hidden class will be updated with a transition path that links to the new hidden class. The conversion of hidden classes is important because it will allow objects to share hidden classes with each other when objects are created from the same way. When two objects share a hidden class and at the same time a property is added simultaneously to both objects, trainsitions will then ensure that both objects will get the same new hidden class with the same functions. the same optimization.

The process is repeated with the next command “this.y = y” (in the Point object, behind the “this.x = x” command).

A new hidden class “C2” is created. Next is a transition class that will be added to “C1” starting with checking if the variable y has been added to the Point object then the hidden class will be changed to “C2”.

The conversion of hidden classes will depend on where the properties are added. See the example below:

Then we can see that p1 and p2 have the same hidden class and transtion class because they are also initialized by the Point function (x, y). But that is not the case. For “p1”, its first property is “a” and then “b” is added. But for “p2”, “b” is added first and then “a”. So “p1” and “p2” actually have 2 hidden classes and 2 different transition paths. So in this case we should initialize and add the properties in the same order so that hidden classes can be reused without creating. (Tip1)

Inline caching

V8 takes advantage of another technique, a type of automated optimization language called inline caching . Inline caching will observe repetitive tasks on an object with the same method. (explain in more detail here ).

We will take a closer look at the general concept of inline caching (in case you don’t have time to read the in-depth explanation above).

So how does it work? That is, it maintains a cache that stores the type of an object passed as a parameter in the newly invoked method. It will then use this information to predict a data type of the object passed in as a parameter the next time it is called. If this prediction is of good quality, it may skip the reference step for each object. It just needs predictive information to refer to the hidden class inside the object.

So how do hidden classes and inline caching refer to each other? Every time a method is called inside an object. The V8 engine will query the hidden class of that object to determine the offset. After 2 times that method is successfully called in the same object with the same hidden class. At this point, V8 will skip the query to the hidden class anymore, just insert the previously defined offset into the pointer to the object itself. With subsequent calls of this method. V8 will default to the method’s hidden class unchanged, and will jump directly to the memory address of each attribute in the method using the previously stored offset without revising calls for each property again. This greatly improves the running speed of the program.

Inline caching is a very important reason for objects of the same type to share hidden classes together. If you create objects of the same type, they do not share the same hidden class (as in the above example). V8 will not be able to use the hidden class mechanism because when the hidden class is different, the offset is also different. The old offset cannot be reused to predict the new.

The two objects basically have the same type, and they also have properties “a” and “b”. But the position is different so they have 2 different hidden classes and of course there will be 2 different offsets.

Compile to machine code

Once the Hydrogen graph has been optimized, Crankshaft will convert it to a lower level code called Lithium. Most Lithium threads are built with specific architectures. Registration and device allocation are performed in this step.

And eventually Lithium will be compiled into machine code. This will then be followed by another operation called OSR: on-stack replacement. Before proceeding with compiling and optimizing a long piece of code, the code is actually still running. At this point V8 will understand this is the start, quite slowly but is to have time to prepare for high-level optimization later. Right in the middle of the execution, V8 will convert the entire context we have present (stacks, or registers) to a more optimized version. This is a fairly complicated process. Let us temporarily understand that this stage is the conversion between optimal algorithms. It can be said that V8 has done inlining right from the beginning. In fact, V8 is not the only engine that performs this complex process.

There are safeguards called deoptimization to perform the reverse conversion and return unoptimized code in the case if the engine being executed is no longer true.

Garbage collection (garbage collection)

For Garbage collection. V8 also applies old algorithms to hide and clean up old versions of code that are no longer in use. The markup is said to be the method to stop executing javaScript code. To control the costs of GC cleanups, and to keep them running smoothly. V8 will implement incremental hacking method. This means that it will not browse the entire heap, trying to hide every possible object. It will only partially browse and then resume normal code execution. The next GC will continue running where the previous one has stopped and continue to browse for another section. It allows for a very small pause during the running of the program. As we mentioned earlier, this cleanup is run in a separate thread from threads that run code or optimize code.

Ignition and TurboFan

A newer release of V8 version 5.9 in 2017. A new automated execution process has been introduced. This process helps to achieve greater performance and significant memory savings for real javaScript applications.

This process is built on Ignition , the V8 compiler and TurboFan – the latest optimized compiler of V8.

You can find out more about V8 team at this blog.

Since version 5.9 of V8, full-codegen and Crankshaft (technology used for V8 since 2010) have been discontinued because the V8 team has struggled to keep up with the features of the new JavaScript and Essential optimizations for these features.

This means the overall V8 will have a simpler architecture and be easier to maintain in the future.

These improvements are just getting started, but it is a pave the way for better optimizations, increasing javaScript performance and narrowing the effects of V8 on chrome and nodejs in the future.

Finally, here are some tips and tricks for better JavaScript code. You can easily compare this with the content above, however, here is a summary for your convenience:

How to write optimized JavaScript

  1. Order of object properties : Try to declare your objects in the same order to have re-use of hidden classes along with previously optimized hidden methods.
  2. Dynamic properties : Adding a property after the object is initialized will cause the engine to change the hidden class and will slow down the methods that have been optimized for the previous methods. Instead, assign the entire property of that object to its own constructor.
  3. Methods : Code that has the same method that is executed repeatedly will run much faster than executing many different methods at the same time. (refer to inline caching)
  4. Arrays : Avoid sparse arrays where its keys are not an increasing number sequence. Sparse arrays are arrays where some of its elements are not a hash. Such arrays are very memory intensive to access. Try not to allocate memory space for large arrays first, and finally do not delete parts of the array, which makes your array sparse.
  5. Tagged values : V8 represents objects and numbers with 32 bits. It uses a bit to identify if it’s an object (flag = 1) or an integer (flag = 0) called SMI (SMall Integer) because of its 31 bits. Then, if a value of a number is greater than 31 bits, V8 will box that number, turn it into a double number, and create a new object to contain that number. Try to use 31-bit numbers whenever possible to avoid collisions between objects inside the JS object.
Share the news now

Source : Viblo