Home  >  Article  >  Web Front-end  >  Understand how JavaScript works, dive into the V8 engine and write optimized code

Understand how JavaScript works, dive into the V8 engine and write optimized code

coldplay.xixi
coldplay.xixiforward
2020-12-08 17:10:562843browse

javascriptThe column introduces in-depth V8 engine and writing optimized code

Understand how JavaScript works, dive into the V8 engine and write optimized code

Related free learning recommendations: javascript( Video)

Overview

A JavaScript engine is a program or interpreter that executes JavaScript code. A JavaScript engine can be implemented as a standard interpreter, or as some form of just-in-time compiler that compiles JavaScript into bytecode.

List of popular projects that implement JavaScript engines:

  • V8 — Open source, developed by Google, written in C
  • Rhino — Managed by the Mozilla Foundation, open source, developed entirely in Java
  • SpiderMonkey — It is the first JavaScript engine to support Netscape Navigator and is currently used by Firefox
  • JavaScriptCore — Open source, sold as Nitro, developed by Apple for Safari
  • KJS — An engine for KDE, originally developed by Harri Porten for the KDE project Konqueror web browser development
  • Chakra (JScript9) — Internet Explorer
  • Chakra (JavaScript) Microsoft Edge
  • Nashorn, as part of OpenJDK, written by Oracle Java Language and Tools Group
  • JerryScript — A lightweight engine for the Internet of Things

Why was the V8 engine created?

The V8 engine built by Google is open source and written in c. This engine is used in Google Chrome, however, unlike other engines V8 is also used in the popular Node.js.
Understand how JavaScript works, dive into the V8 engine and write optimized code

V8 was originally designed to improve the performance of JavaScript execution in web browsers. To gain speed, V8 converts JavaScript code into more efficient machine code instead of using an interpreter. It compiles JavaScript code into execution-time machine code by implementing a JIT (Just-In-Time) compiler, just like many modern JavaScript engines such as SpiderMonkey or Rhino (Mozilla) do. The main difference here is that V8 does not generate bytecode or any intermediate code.

V8 had two compilers

Before the 5.9 version of V8 came out, the V8 engine used two compilers:

  • full-codegen — one Simple and very fast compiler that produces simple and relatively slow machine code.
  • Crankshaft - A more sophisticated (Just-In-Time) optimizing compiler that generates highly optimized code.

The V8 engine also uses multiple threads internally:

  • The main thread does what you would expect: gets the code, compiles it and executes it
  • There is also a separate thread for compilation, so the main thread can continue executing while the former optimizes the code
  • A Profiler thread that tells the runtime we spent a lot of time so Crankshaft can optimize them
  • Some threads handle the garbage collector

When JavaScript code is executed for the first time, V8 uses the full-codegen compiler to directly translate the parsed JavaScript into machine code without any Convert. This allows it to start executing machine code very quickly. Note that V8 does not use intermediate bytecode, eliminating the need for an interpreter.

When the code has been running for a while, the analysis thread has collected enough data to determine which method should be optimized.

Next, Crankshaft starts optimization from another thread. It converts JavaScript abstract syntax trees into a high-level static single allocation (SSA) representation called Hydrogen and attempts to optimize the Hydrogen graph, most optimizations are done at this level.

Inline code

The first optimization is to inline as much code as possible ahead of time. Inlining is the process of replacing the call site (the line of code that calls the function) with the body of the called function. This simple step allows the following optimizations to make more sense.

Understand how JavaScript works, dive into the V8 engine and write optimized code

Hidden Classes

JavaScript is a prototype-based language: classes and objects are not created using a cloning process. JavaScript is also a dynamic programming language, which means that properties can be easily added or removed from an object after instantiation.

Most JavaScript interpreters use a dictionary-like structure (based on a hash function) to store the location in memory of object property values. This structure makes retrieving property values ​​in JavaScript faster than in Java or C# Computational costs are higher in non-dynamic programming languages.

In Java, all object properties are determined by a fixed object layout before compilation, and cannot be added or removed dynamically at runtime (of course, C# has dynamic typing, which is another topic).

Thus, attribute values ​​(or pointers to these attributes) can be stored in memory as contiguous buffers, with fixed offsets between each buffer. The length of the offset can be easily determined based on the attribute type, while in You can change property types at runtime which is not possible in JavaScript.

Because using a dictionary to find the location of an object's properties in memory is very inefficient, V8 uses a different approach: hidden classes. Hidden classes work similarly to fixed objects (classes) used in languages ​​like Java, except that they are created at runtime. Now, let's look at their actual example:

Understand how JavaScript works, dive into the V8 engine and write optimized code

Once the "new Point(1,2)" call occurs, V8 will create a hidden named "C0" kind.

Understand how JavaScript works, dive into the V8 engine and write optimized code

No properties have been defined for Point, so "C0" is empty.

Once the first statement "this.x = x" is executed (within the "Point" function), V8 will create a second hidden class named "C1", which is based on "C0" . "C1" describes the location in memory (relative to the object pointer) where property x can be found.

In this case, "x" is stored at offset 0, which means when considering the point object in memory as a contiguous buffer, the first offset will correspond to the attribute "x" . V8 will also update "C0" with a "class transformation" which states that if the attribute "x" is added to the point object, the hidden class should switch from "C0" to "C1". The hidden class of the point object below is now "C1".

Understand how JavaScript works, dive into the V8 engine and write optimized code

#Every time a new property is added to an object, the old hidden class is updated with a transformation path pointing to the new hidden class. Hidden class casts are important because they allow hidden classes to be shared between objects created in the same way. If two objects share a hidden class and the same property is added to them, the transformation will ensure that both objects receive the same new hidden class and all the optimization code that comes with it.

The same process is repeated when the statement "this.y = y" is executed (inside the "Point" function, after the "this.x = x" statement).

A new hidden class named "C2" will be created. If a property "y" is added to a Point object (which already contains the property "x"), a class transformation will be added to "C1" , then the hidden class should be changed to "C2", and the hidden class of the point object should be updated to "C2".

Understand how JavaScript works, dive into the V8 engine and write optimized code

Hidden class conversion depends on the order in which properties are added to the object. Take a look at the following code snippet:

Understand how JavaScript works, dive into the V8 engine and write optimized code

Now, assume that for p1 and p2, the same hidden class and transformation will be used. So, for "p1", first add attribute "a", then add attribute "b". However, "p2" is assigned "b" first, then "a". Therefore, "p1" and "p2" end up with different hidden categories due to different transformation paths. In this case it is much better to initialize the dynamic properties in the same order so that the hidden class can be reused.

Inline caching

V8 utilizes another technique for optimizing dynamically typed languages ​​called inline caching. Inline caching relies on the observation that repeated calls to the same method tend to occur on the same type of object. An in-depth explanation of inline caching can be found here.

The general concept of inline caching will be discussed next (if you don’t have time to go through the in-depth understanding above).

So how does it work? V8 maintains a cache of object types passed as arguments in recent method calls and uses this information to predict object types passed as arguments in the future. If V8 could predict the type of object passed to a method well enough, it could bypass the process of how to access the object's properties and instead use stored information from previous lookups to the object's hidden class.

So how are the concepts of hidden classes and inline caching related? Whenever a method is called on a specific object, the V8 engine must perform a lookup of that object's hidden class to determine the offset at which to access the specific property. After two successful calls to the same hidden class, V8 omits the lookup of the hidden class and simply adds the property's offset to the object pointer itself. For all next calls to this method, the V8 engine assumes that the hidden class has not changed and jumps directly to the memory address of the specific property using the offset stored from the previous lookup. This greatly improves execution speed.

Inline caching is also why it is important for objects of the same type to share hidden classes. If you create two objects of the same type and different hidden classes (as we did in our previous example), V8 will not be able to use inline caching because even though the two objects are of the same type, their corresponding hidden classes are Its properties are assigned different offsets.

Understand how JavaScript works, dive into the V8 engine and write optimized code

The two objects are basically the same, but the "a" and "b" properties are created in a different order.

Compile to machine code

Once the Hydrogen graph is optimized, Crankshaft reduces it to a lower-level representation called Lithium. Most Lithium implementations are architecture specific. Register allocation often occurs at this level.

Finally, Lithium is compiled into machine code. Then there is OSR: on-stack replacement. Before we start compiling and optimizing an explicit long-running method, we might run stack replacement. V8 doesn't just slowly perform stack replacement and start optimizing again. Instead, it converts all the context we have (stack, registers) to switch to the optimized version during execution. This is a very complex task, considering that among other optimizations, V8 initially inlines the code. The V8 isn't the only engine that can do it.

There is a safety measure called deoptimization that does the opposite conversion and returns unoptimized code assuming the engine is invalid.

Garbage Collection

For garbage collection, V8 uses the traditional mark-and-sweep algorithm to clean up the old generation. The marking phase should stop JavaScript execution. To control GC costs and make execution more stable, V8 uses incremental marking: instead of walking the entire heap and trying to mark every possible object, it just walks a portion of the heap and then resumes normal execution. The next GC stop will continue where the previous heap walk left off, which allows for a very brief pause during normal execution, as previously mentioned the scan phase is handled by a separate thread.

How to write optimized JavaScript

  1. Order of object properties: Always instantiate object properties in the same order so that hidden classes and subsequent optimizations can be shared code.
  2. Dynamic properties: Because adding properties to an object after instantiation will force a hidden class change and slow down the execution of all methods optimized by the previously hidden class, it is important to All object properties are assigned in the constructor.
  3. Method: Code that executes the same method repeatedly will run faster than code that only executes multiple different methods once (due to inline caching).
  4. Array: Avoid sparse arrays, where the key values ​​are not self-increasing numbers, and sparse arrays that do not store all elements are hash tables. Accessing elements in such arrays is expensive. Also, try to avoid preallocating large arrays. Better to grow on demand. Finally, don't delete elements from the array, as this will make the keys sparse.
  5. Tag Value: V8 uses 32 bits to represent objects and values. Since the value is 31 bits, it uses one bit to distinguish whether it is an object (flag = 1) or an integer called SMI (SMall Integer) (flag = 0). Then, if a number is larger than 31 digits, V8 will box the number, turn it into a double, and create a new object to hold the number. Use 31-bit signed numbers whenever possible to avoid expensive boxing operations on JS objects.

Ignition and TurboFan

With the release of V8 5.9 earlier in 2017, a new execution pipeline was introduced. This new pipeline enables greater performance gains and significant memory savings in real JavaScript applications.

The new execution flow is built on Ignition (V8's interpreter) and TurboFan (V8's latest optimizing compiler).

Since the release of V8 5.9, as the V8 team has struggled to keep up with new JavaScript language features and the optimizations these features require, the V8 team has no longer used full-codegen and Crankshaft (since 2010) Serviced by V8 Technology).

This means that V8 will have a simpler and more maintainable architecture overall.

Understand how JavaScript works, dive into the V8 engine and write optimized code

These improvements are just the beginning. The new Ignition and TurboFan pipelines pave the way for further optimizations that will improve JavaScript performance and shrink V8’s footprint in Chrome and Node.js for years to come.

Related free learning recommendations: php programming (video)

The above is the detailed content of Understand how JavaScript works, dive into the V8 engine and write optimized code. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:segmentfault.com. If there is any infringement, please contact admin@php.cn delete