Personal Summary:
A JavaScript engine is implemented by a standard interpreter, or by an instant compiler.
Interpreter (interpreter): interprets one line and executes one line.
Compiler (Compiler): all compiled into machine code, unified execution. (reduces the overhead of switching and scheduling, faster.) )
The V8 engine is an instant compiler.
Optimization strategy for V8 engine:
1. Inline: Replace the expert code called by the function with the called function body.
2. Hidden classes: Most dynamic languages use the structure of a class dictionary (based on a hash function) to store the memory address of the object's property value in memory (that is, the object's memory address), so it is slower than the non-dynamic language (java,c#).
V8 increases efficiency by using "hidden classes".
3. Inline caching: For frequently used properties, V8 ignores the lookup of the hidden class and simply adds the displacement of the property to the object pointer itself.
4. Garbage collection: V8 uses traditional mark-and-purge techniques.
5 tips on how to write the best code in the V8 engine
This is the second chapter of how JavaScript works.
This chapter will delve into the internal structure of Google's V8 engine. We also offer a few tips on how to write better JavaScript code.
Overview
A JavaScript engine is a program or an interpreter that runs JavaScript code. A JavaScript engine can be implemented using either a standard interpreter or an instant compiler , which in some way interprets JavaScript as a byte code.
Here are a series of popular projects to implement the JavaScript engine:
- v8-by Google Open Source in the C + + language
- Rhin-is led by the Mozilla Foundation, open source, and fully developed using Java.
- The spidermonkey-JavaScript engine, which was previously powered by the Netscape browser, is now used by Firefox.
- Javascriptcore-Open source, to Nitro name to promote, and by Apple for Safari development.
- The KJS-KDE engine was originally developed by Harri Porten, a Konqueror browser for the KDE project.
- Chakra (JSCRIPT9)-ie
- Chakra (JavaScript)-microsoft Edge
- nashorn-is open source as part of the OpenJDK, written by the Oracle Java language and Tool Group.
- jerryscript-a lightweight Internet of things engine.
The origin of the V8 engine
The V8 engine is open source by Google and written in the C + + language. Google Chrome has built this engine. The V8 engine differs from other engines in that it is also used in the popular node. JS runtime.
Initially V8 was designed to optimize the performance of JavaScript in Web browsers. To achieve faster execution speed, V8 translates JavaScript code into more efficient machine code rather than using an interpreter. It compiles JavaScript code into machine code at run time by implementing an instant compiler, like many modern JavaScript engines such as SpiderMonkey or Rhino (Mozilla). The main difference is that V8 does not produce bytecode or any intermediate code.
V8 once had two compilers
Before the birth of V8 5.9 (beginning of 2017), the engine had two compilers:
- full-codegen-a simple and fast compiler to produce simple and relatively slow machine codes.
- crankshaft-a more complex (instant) optimization compiler is used to produce efficient code.
Multiple threads are also used inside the V8 engine:
- The main thread does what you expect-crawl your code, compile and execute
- There is a separate thread to compile the code, so the main thread can remain executing while the former is optimizing the code
- A thread for performance detection tells the runtime how much time we spend on which method, so that crankshaft can optimize the code
- There are several threads that are used to handle the garbage collector cleanup work.
When the JavaScript code is executed for the first time, V8 uses Full-codegen to directly interpret the parsed JavaScript code as a machine code with no transitions in between. This makes it very fast to run the machine code at first. Notice that the V8 is not represented by an intermediate bytecode, so there is no need for an interpreter.
After the code has been executed for some time, the performance detector thread has collected enough data to tell crankshaft which method can be optimized.
Next, start the Crankshaft code optimization in another thread. It transforms the JavaScript syntax abstraction tree into an advanced static single assignment called hydrogen and tries to optimize the hydrogen chart. Most of the code optimizations occur at this level.
Inline
The first optimization method is to inline the code as much as possible ahead of time. Inline refers to the process of replacing the calling address (the line of code called by the function) with the function body of the called function. This simple step makes the next code optimization more meaningful.
Hide Class
JavaScript is a prototype-based language: There are no classes and objects to create when cloning. JavaScript is also a dynamic programming language, which means that properties can be arbitrarily added or removed after it is instantiated.
Most JavaScript interpreters use the structure of a class dictionary (based on a hash function) to store the memory address of an object's property value in memory (that is, the object's memory address). This structure makes it more time consuming to get property values in JavaScript than in non-dynamic programming languages such as Java or C #. In Java, all object properties are determined by a fixed object layout before compilation and cannot be dynamically added or deleted at run time (well, C # has a dynamic type, which is another topic). Therefore, property values (pointers to these properties) are stored in memory in contiguous buffers, with fixed displacements between each other. The length of the displacement can be easily computed based on the attribute type, but this is not possible in JavaScript, because the runtime can change the property type.
Because memory addresses that use dictionaries to find object properties in memory are very inefficient, V8 instead uses hidden classes. Hidden classes work similar to fixed object layouts (classes) used in the Java language, except that they are created at run time. Now, let's see what they look like:
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(1, 2);
Once the "new point" call occurs, V8 creates a hidden class called "C0".
Because the attribute has not been created for class point, "C0" is empty.
Once the first statement "this.x = x" starts executing (in the point function), V8 will create a second hidden class based on "C0". "C1" describes the memory address (relative to the object pointer) where the X attribute can be found. In this example, "x" is stored in displacement 0, which means that when the point object is viewed in an in-memory contiguous buffer, the start of the shift is consistent with the attribute "X". V8 will use class conversion to update "C0", which means that the attribute "X" is added to the point object, and the hidden class will be changed from "C0" to "C1". The following hidden classes of point objects are now "C1".
Whenever the object adds a new property, use the transform path to update the old hidden class to the new hidden class. It is important to hide class conversions because they enable objects created in the same way to share hidden classes. If two objects share a hidden class and two objects have the same properties added, the conversion guarantees that two objects receive the same new hidden class and that all the optimized code will contain these new hidden classes.
When you run the "This.y = y" statement, the same procedure is repeated (or in the point function, after the "this.x = x" statement).
A hidden class called "C2" is created, and a class conversion is added to "C1" to indicate whether the property "Y" is added to the point object (already owns the attribute "X") and then the hidden class is changed to "C2", then the hidden classes of the point object are updated to "C2".
The Hidden class transformation relies on the order in which properties are added to the object. Look at the following code snippet:
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(1, 2);
p1.a = 5;
p1.b = 6;
var p2 = new Point(3, 4);
p2.b = 7;
p2.a = 8;
Now, you'll assume that P1 and P2 will use the same hidden classes and class conversions. However, for "P1", add the property "a" and then add the property "B". For "P2", first add the attribute "B" and then "a". This way, because different conversion paths are used, "P1" and "P2" use different hidden classes. In this case, a better approach would be to initialize the dynamic properties in the same order to facilitate reuse of the hidden classes.
Inline cache
V8 utilizes another technique for optimizing dynamic type languages called inline caching. Inline caching relies on the observation of repeated invocations of the same method for the same type of object. Here's an in-depth article on inline caching.
We will be exposed to the approximate concept of inline caching (in case you don't have time to read through the above-in-depth understanding of inline cache articles).
How does it work? V8 maintains a cache of object types that pass in the recently invoked method as a parameter, and then uses this information to assume that the object type will be passed into this method at some point in the future. If V8 is able to pre-contract the object type of the incoming method, it can bypass the process of finding how to access the object's properties, instead using the stored information from the object hidden class previously found.
So how does the concept of hidden classes relate to inline caching? Whenever a method is called on a specified object, the V8 engine has to perform an operation to find the object's hidden class, which is used to obtain a displacement that accesses the specified property. After a successful call to the same method of the same hidden class two times, V8 ignores the lookup of the hidden class and simply adds the displacement of the property to the object pointer itself. After all the calls to this method, the V8 engine assumes that the hidden class has not changed and then jumps directly to the memory address of the specified property using the displacement previously found. This greatly increases the speed of your code.
Memory caching is why it is so important to share hidden classes with objects of the same type. When you create two objects of the same type and use different hidden classes (as in the previous example), V8 will not be able to use the memory cache, because even two objects of the same type, their corresponding hidden classes, assign different address displacements for their properties.
These two objects are basically the same, but the order of creating "a" and "B" is different
Compile to machine code
Once the hydrogen chart is optimized, crankshaft will downgrade it to a low-level representation called Lithium. Most implementations of Lithium are dependent on the specified schema. Register allocations occur at this level.
Finally, the Lithium will be compiled into machine code. Then something else called OSR happened: stack substitution. Before starting to compile and optimize an obvious time-consuming method, it was very possible to run it in the past. V8 will not forget where the code is performing slowly, and use the optimized version code again. Instead, it transforms all the contexts (stacks, registers) so that you can switch to the optimized version code during execution. This is a complex task, and all you need to remember is that in other optimizations, V8 initializes the inline code. V8 is not the only engine that has this ability.
There is a security guard called inverse optimization, which prevents the reverse conversion and the reversal of the code to the code that is not optimized when the engine assumes that something is not happening.
Garbage collection
V8 uses traditional mark-and-erase techniques to clean up old memory for garbage collection. The tagging phase aborts the operation of the JavaScript. To control the cost of garbage collection and to make code execution more stable, V8 uses incremental notation: not traversing the entire memory heap, trying to tag every possible object, simply traversing a portion of the heap and restarting normal code execution. The next garbage collection point is executed from where the last heap traversal was aborted. This will have a very short gap during normal code execution. As mentioned before, the purge phase is handled by a separate thread.
Ignition and turbofan
With the release of the V8 5.9 release earlier in 2017, a new execution pipeline was brought in. The new pipeline has achieved greater performance gains and significantly saved memory in real-world JavaScript programs.
The new execution pipeline is built on the new V8 interpreter ignition and V8 the latest optimizer compiler turbofan.
You can check the V8 team's blog post.
Since the release of V8 5.9, Full-codegen and Crankshaft (V8 from 2010) are no longer used by V8 to run JavaScript because the V8 team is working to keep up with the new JavaScript language features and Do the optimization.
This means that the entire V8 will be leaner and more maintainable.
Promotion of Web pages and node. JS Benchmarks Ratings
These improvements are just a start. The new ignition and turbofan pipelines pave the future for optimizations that will improve JavaScript performance and reduce V8 traces in Chrome and node. js over the next few years.
Finally, here are some how to write well-optimized, better JavaScript code. You can easily summarize from the above content, however, for your convenience, here is a summary:
How to write optimized JavaScript code
- Order of Object properties: Always instantiate your object properties in the same order, so that your hidden classes and subsequent optimization code can be shared.
- Dynamic properties: Adding properties to an object after instantiation causes the method to be optimized for previously hidden classes to become slower. Instead, all properties of the object are assigned in the object constructor.
- Method: Code that repeats the same method is faster than code that runs a different method at a time (thanks to inline caching).
- Sequence: Avoid using a sparse sequence of keys that are not incrementing numbers. The number of columns in a sparse sequence that does not contain each element is called a hash table. Accessing the elements in the sequence can be more time-consuming. Similarly, try to avoid allocating large arrays in advance. It's best to increment as you use it. Finally, do not delete the elements in the sequence. This causes the key to be sparse.
- Tagged Value: V8 uses 32 bits to represent objects and numbers. It uses one to identify the object (flag=1) or an integer (flag=0) called an SMI (small integer), which is a small integer because it is 31 bits. Then, if a value is larger than 31 bits, V8 will box the number, convert it to a floating point, and create a new object to store the number. Try using 31-bit signed numbers to avoid the time-consuming boxing operations of creating JS objects.
How JavaScript works (JavaScript works) (ii) engine, runtime, how to write the best code in the V8 engine 5 tips