If you have dreams and dry goods, you can search for [Great Move to the World] on WeChat and pay attention to this Shawanzhi who is still washing dishes in the early hours of the morning.
This article GitHub https://github.com/qq449245884/xiaozhi has been included, there are complete test sites, materials and my series of articles for interviews with first-line manufacturers.
Hi! Hello everyone, I think everyone who clicked here should have heard and executed JavaScript on the browser or Node.js, but have you ever thought about how JavaScript is executed? The hero behind this is the JavaScript engine, and the V8 engine mentioned in the title is one of them!
The V8 engine is a JavaScript and WebAssembly engine open sourced by Google with C++. Currently, Chrome and Node.js use V8 to execute JavaScript. In addition to V8, there are other JavaScript engines such as SpiderMonkey (the earliest JavaScript engine, currently used by the Firefox browser) and JavaScriptCore (used by the Safari browser).
OK, so how exactly does the V8 engine execute JavaScript?
V8 engine execution process
Scanner
The first step after the V8 engine obtains the JavaScript source code is to let Parser use the Tokens provided by Scanner
(there are grammatical keywords in JavaScript in Tokens, such as function、async、if
, etc.), The source code of JavaScript is parsed into abstract syntax tree , which is the AST (abstract syntax tree) that you often see in related articles.
If you are curious about what an AST looks like, you can use the JavaScript Parser acron , or this website to generate an AST reference. Here is the code using acron:
const { Parser } = require('acorn')
const javascriptCode = `
let name;
name = 'Clark';
`;
const ast = Parser.parse(javascriptCode, { ecmaVersion: 2020 });
console.log(JSON.stringify(ast));
Below is the parsing let name; name = 'Clark'
; the resulting AST:
{
"type": "Program",
"start": 0,
"end": 31,
"body": [
{
"type": "VariableDeclaration",
"start": 3,
"end": 12,
"declarations": [
{
"type": "VariableDeclarator",
"start": 7,
"end": 11,
"id": {
"type": "Identifier",
"start": 7,
"end": 11,
"name": "name"
},
"init": null
}
],
"kind": "let"
},
{
"type": "ExpressionStatement",
"start": 15,
"end": 30,
"expression": {
"type": "AssignmentExpression",
"start": 15,
"end": 29,
"operator": "=",
"left": {
"type": "Identifier",
"start": 15,
"end": 19,
"name": "name"
},
"right": {
"type": "Literal",
"start": 22,
"end": 29,
"value": "Clark",
"raw": "'Clark'"
}
}
}
],
"sourceType": "script"
}
If you go further and convert the above AST into a graph, it will look like this:
The AST can understand the steps it is performing from top to bottom, left to right:
- Go to VariableDeclaration to create a variable named
name
- go
ExpressionStatement
to the expression - Go
AssignmentExpression
encounter=
, and the left isname
, the right is the stringClark
After the AST is generated, the first step of the V8 engine is completed.
JIT (Just-In-Time)
The Chinese name of JIT is just-in-time compilation, which is also the way that the V8 engine compiles JavaScript at execution time.
There are several ways to turn the code into an executable language. The first is a compiled language, such as C/C++. When the code is written, the compiler will first convert the code into machine code before it can be executed. The second is like JavaScript, which interprets the code into a language that the machine understands when it is executed. This kind of interpretation and execution is called a literal translation language.
The advantage of a compiled language is that you can review all the code in the compilation phase before execution, and complete all the optimizations that can be done, but the literal translation language cannot do this, because the interpretation starts at the time of execution, and the execution is relatively It is slow, and there is no way to optimize it at the beginning. In order to deal with this situation, JIT appeared.
JIT combines interpretation and compilation, so that when executing JavaScript, it can analyze the intelligence of the code execution process, and when sufficient intelligence is obtained, the relevant code can be recompiled to achieve faster machine code.
JIT sounds awesome, and the left and right hands in the V8 engine are Ignition and TurboFan .
Ignition & TurboFan
After the AST is successfully parsed, Ignition
will interpret --- AST
as ByteCode
and become an executable language, but the V8 engine has not ended here, Ignition When ByteCode is executed, it collects the type information of the code at the time of execution. For example, if we have a sum
function, and always make sure that the parameter type of the call is number
, then Ignition will log it.
At this point, TurboFan on the other hand will check the information, when it confirms the information that "only number
type parameters will be sent sum
this function executes" When , optimization will be carried out, --- e236406786e5b0b37e11c9efd88f1c4e sum
from ByteCode and then compiled into faster machine code execution.
In this way, the characteristics of the JavaScript literal translation language can be preserved, and the performance can be optimized at the time of execution.
But after all, it is JavaScript, and no one can guarantee that the parameters sent in the one millionth time are still number
, so when sum
the parameters received are not consistent with the previous optimization strategy. At the same time, the Deoptimization action will be performed.
TurboFan's Optimization does not directly convert the original ByteCode into machine code, but adds a Checkpoint between the ByteCode and the machine code while generating the machine code. Before executing the machine code, the Checkpoint will be used to check whether it matches the previous one. The type of Optimization matches. In this way, when sum
is called with a different type from Optimization, it will be blocked in Checkpoint, and Optimization will be performed.
Finally, if TurboFan repeats the process of Optimization and Deoptimization 5 times, it will directly give up the treatment and will not help this function to do optimization.
So how do you know if TurboFan really does optimization? We can experiment with the following code:
const loopCount = 10000000;
const sum = (a, b) => a + b;
performance.mark('first_start');
for (let i = 0; i < loopCount; i += 1) {
sum(1, i);
}
performance.mark('first_end');
performance.mark('second_start');
for (let i = 0; i < loopCount; i += 1) {
sum(1, i);
}
performance.mark('second_end');
performance.measure('first_measure', 'first_start', 'first_end');
const first_measures = performance.getEntriesByName('first_measure');
console.log(first_measures[0]);
performance.measure('second_measure', 'second_start', 'second_end');
const second_measures = performance.getEntriesByName('second_measure');
console.log(second_measures[0]);
The above uses perf_hooks of Node.js v18.1 to measure the execution speed. The execution results are as follows:
After execution, you will find that the first execution time took 8 seconds, and the second execution time only took 6 seconds. You can change the number loopCount
to a larger number, and the gap will become more and more obvious. .
However, there is still no way to confirm that TurboFan has acted, so when executing next, add the flag of --trace-opt
to see if Optimization occurs:
The information after execution shows several optimizations performed by TurboFan, and also writes down the reasons for each optimization. For example, the first and second lines show the reasons as hot and stable and small function respectively. These are the optimization strategies behind TurboFan.
What about the Optimization part? It is also very simple to test. Just change the parameter type of the second loop to String and send it to the sum function for execution, then TurboFan will perform Deoptimization. In order to view the Deoptimization message, add --trace-deopt
:
In the highlighted section, Deoptimization is executed because the parameter types sent to sum are different, but then because String is always sent into sum for execution, TurboFan will re-optimize sum.
Summarize
After sorting out the process of executing JavaScript by the V8 engine, the following flowchart can be drawn:
Combined with the above diagram to illustrate how the V8 engine executes JavaScript:
- Parser parses JavaScript into AST through Scanner's Tokens
- Ignition interprets AST as ByteCode execution, and collects type information during execution
- TurboFan recompiles ByteCode to machine code for information
- If the machine code checks that this execution is different from the previous optimization strategy, it will do Deop timization and return to ByteCode to continue collecting type information or give up treatment.
Author: God Q Superman> Source: medium
comminicate
The article is continuously updated every week. You can search for "Big Move to the World" on WeChat to read and update it as soon as possible (one or two articles earlier than the blog). This article GitHub https://github.com/qq449245884/xiaozhi has been included and sorted. Many of my documents, welcome to Star and perfect, you can refer to the test center to review the interview, and pay attention to the public account, reply to the welfare in the background, you can see the welfare, you know.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。