V8 with javascript
introduce
V8 is an open source JavaScript (interpreted line language) engine developed by Google. Written in C++ (compiled language), V8 is currently used in the Chrome browser and node.js. Its core function is to execute human-readable JavaScript code. The V8 engine has made significant improvements in JavaScript performance optimization, making it a popular open source high-performance JavaScript engine.
\
\
background
The Google (Denmark) team started working on V8 in 2006, Part of the problem was that Google wasn’t satisfied with the execution speed of existing JavaScript engines, and when Chrome launched in 2008, it quickly took over the market with a huge speed advantage, with 59% of the market in 2017.
application
Chrome’s JS engine is V8 and Nodejs runs on a V8 engine. Electron’s underlying engine is also V8
why
Here’s what compiled and interpreted languages are:
Compiled languages: Special compilation procedures must be performed before a program is executed, with the following characteristics:
- The source code can be compiled into machine language only once, and the subsequent execution does not need to be recompiled, but directly uses the previous compilation results. Therefore, its execution efficiency is relatively high;
- Compiled languages: C, C++, Java, Pascal/Object Pascal (Delphi);
Program execution efficiency is relatively high, but more dependent on the compiler, so the cross-platform poor;
- Different platforms have a great impact on the compiler.
- On 16-bit systems, an int is 2 bytes (16 bits), whereas on 32-bit systems, an int is 4 bytes (32 bits).
- In 32-bit systems, long takes up 4 bytes, while in 64-bit systems long takes up 8 bytes.
Interpretive language – interpretive line language, support dynamic type, weak type, in the program to run the time to compile, and before the compilation needs to determine the type of variables, relatively low efficiency, for different system platforms have greater compatibility.
- The source code can not be directly translated into machine language, but first translated into intermediate code, and then interpreted by the interpreter to run the intermediate code;
- Source code – > Intermediate code – > machine language
- Programs do not need to be compiled; they are translated into machine language at runtime, every time they are executed.
- Interpretive languages: Python, JavaScript, Shell, Ruby, MATLAB, etc.
- The running efficiency is generally relatively low, depends on the interpreter, and has good cross-platform performance.
Comparison:
- In general, compiled languages run more efficiently than interpreted languages; However, some interpreters for interpreted languages can even outperform compiled languages by dynamically optimizing their code at run time.
- Compiled languages are less cross-platform than interpreted languages;
Into the above description, interpretive language, low efficiency, with the development of Web related technology, JavaScript to undertake more and more work, has long gone beyond the “form verification” category, which requires more rapid parsing and execution of JavaScript scripts. The V8 engine was designed to solve this problem and is used in Node to parse JavaScript.
How does a programming language work
It is well known that the programs we do through programming languages are run through processors. However, the processor cannot directly understand the code we write in high-level languages (such as C++, Go, JavaScript, etc.), only machine code, so it needs to Go through a series of steps to translate the code we write into machine language before executing the program. This process is usually performed by a Compiler or Interpreter.
So how does the compiler and interpreter work?
The figure above gives an idea of how they work. So why do compilers and interpreters exist at the same time when both can do the job of translating code?
This is because programming languages fall into two categories: statically typed and dynamically typed. Statically typed languages, such as C++, Go, etc., need to be AOT compiled into machine code and then executed. This process is mainly done by the compiler. Dynamic languages, such as JavaScript, Python, and so on, only perform compile-execution (JIT) at runtime, which is done through the interpreter.
From the above description, we already know that JavaScript is translated by the interpreter. What about the detailed process of JavaScript engine V8 executing Js code? Let’s break it down.
\
V8 executes the overall flow of Js code
Along the way, V8 uses the Parser, Ignition, and TurboFan to execute the Js code.
When V8 executes javascript source code, the parser parses the source code into an abstract syntax tree (AST), which is then translated into bytecode and executed as it is interpreted. During this process, the interpreter keeps track of how many times a particular snippet of code has been run, and if it has been run more than a certain threshold, it is marked as hot code. The running information is fed back to the optimization compiler. The optimization compiler optimizes and compiles bytecode based on the feedback. The optimized machine code is eventually generated, and when the code continues to execute, the interpreter directly uses the optimized machine code to execute without having to interpret it again, thus greatly improving the efficiency of the code. This technique of compiling code at run time is also known as JIT (just-in-time compilation). The performance of JavaScript code can be greatly improved by JIT.
1.Parser generates abstract syntax trees
Once you start downloading Javascript files in Chrome, the Parser starts parsing the code in parallel on a separate thread. This means that parsing can be done in just a few milliseconds after the download is complete and an AST is generated.
\
The diagram above shows the structure of a Js code after it is transformed into an AST. As can be seen from the diagram, the AST structures the code into a tree structure for better understanding by the compiler or interpreter. In addition, AST is widely used in various projects, such as Babel, ESLint, so what is the generation process of AST?
1. Lexical analysis: basically, char stream is converted to token stream, which is our line by line of code, and token is the smallest single character or string that is syntactically non-divisible.
Var name = "ivWeb"; var name = "ivWeb"; var name = "ivWeb"; var name = "ivWeb"; "name" }, { "type": "Punctuator", "value": "=" }, { "type": "String", "value": ""ivweb"" }, { "type": "Punctuator", "value": ";" } ]Copy the code
\
Var name = “ivWeb “; Such a code would have the keyword “var”, identifier “name”, assignment operator “=”, string “ivWeb”, delimiter “;” , a total of 5 tokens. 2. Syntax analysis: Based on the syntax rules, the previously generated token stream is formed into a syntax rule tree with element hierarchy nesting, which is called AST. During this process, if the source code does not conform to the syntax rules, it is terminated and a “syntax error” is thrown.
2.Ignition generates bytecode
\
Bytecode is an abstraction of machine code that can be viewed as the small building blocks that together make up any JavaScript functionality. Bytecode takes up less memory than machine code, which is an important reason why V8 uses bytecode. Bytecode cannot be run directly on the processor; it must be converted to machine code by the interpreter before it can be executed.
\
As you can see from the figure above, Ignition takes the AST from the previous step and generates bytecode through a bytecode generator with a few columns of optimization. In the process:
- Register Optimizer: a Register Optimizer is designed to avoid unnecessary loading and storage of registers.
- Peephole Optimizer: Finds portions of direct code that can be reused and merges them.
- Dead-code Elimination: Eliminate unnecessary code and reduce the size of bytecode
Optimize the above three processes to further reduce the size of the bytecode and improve performance, and finally Ignition to implement the optimized bytecode.
3. Execute code and optimizations
\
Ignition executes the bytecode generated in the previous step and records information such as how many times the code has been run. If the same code has been run many times, it is flagged as “HotSpot” and sent to the compiler TurboFan. TurboFan then compiles it into more efficient machine code and stores it. The next time it executes the code, it replaces the bytecode with the current machine code, making the code much more efficient. In addition, when TurboFan decides that a piece of code is no longer hot code, it performs de-tuning, throws away the optimized machine code, and returns to Ignition.