In the last article, we introduced the execution pipeline architecture of the V8 engine. This article focuses on V8 syntax parsing. The original video is a product manager thinking; This article is about science and engineering thinking; The parsing phase is especially important for the front end and is weaker than the Noder because parser only affects the startup and early running phases of the application. For front-end students, it is customary to introduce some large libraries and only use one or two functions. Such as lodash. How much does this affect performance?
Or conclusion first
- V8 has two modes of parsing: the eager parser (full) and the lazy preparser (fast). Although lazy parsing is twice as fast as eager, lazy can result in 1.5 times the parsing time; If lazy is preresolved, the eager will be resolved again. You can force eager to run with optimize.js
- JavaScript syntax parsing speed: 1MB/S. Parsing 400K JavaScript takes about 370ms. You can use the Chrome browser address bar
chrome://tracing
Check the specific time; - Front-end pages run as little JavaScript code as possible; The parser also has a cache, which caches bytecode, and if the bundle is used, updating the bundle will invalidate the entire bundle.
Computer compiler principle is briefly introduced
This article requires some background knowledge of computer compilation principle. So I feel I need to add that the principle of computer compilation is to convert the code that human can understand into the code that machine can understand, and the machine only knows the machine language instructions when it executes. Usually computer high-level languages need to go through: source -> parsing -> intermediate code generation -> code optimization -> object code generation -> object program. V8 is no exception: JS source code -> parse -> generate bytecode -> compiler -> converter -> Run code. The syntax parsing phase generates syntax trees and scopes, which is to turn each line of our code into a syntax tree structure for disambiguation, code analysis, and binding scopes. You can use esprima to see what the JavaScript syntax tree looks like: esprima.org/demo/parse…. .
This article focuses on V8’s parsing process, the result of which is bytecode (intermediate code). The next article looks at running the V8 compiler.
Lazy is better than eager?
What is JavaScript parsing?
We started with the JavaScript execution pipeline from the previous article. The red part of the figure below is the parsing process. It’s actually the compile phase of JavaScript. Although the compilation process does not participate in the “runtime phase of JavaScript (shown in blue),” parsing of JavaScript as a dynamic scripting language does trigger parsing of code changes and actual execution.
Why do we care about parsing?
-
A typical one-page Web application:
- You need to load 0.4MB of JavaScript;
- It takes about 370 milliseconds; (Tested on mobile phone model Moto G4)
- -> The speed of parsing is ~ 1MB/s
How does V8 handle JavaScript parsing? eager parse & lazy parse
This is V8’s own implementation to improve the parsing speed of JavaScript files; Currently the official specification for non-javascript engines.
- Two parsing modes: eager (full parsing mode) and lazy (fast parsing mode)
- Why is it so hard to parse JavaScript code?
Two kinds of parsers
-
Parser: Full parse mode, “eager”
- Parse the function we want to compile;
- Construct syntax tree;
- Build function Scopes (Scopes);
- Find all grammatical errors;
-
Pre-parsers: fast parsing mode, “lazy”
- To skip functions that we don’t want compiled;
- Variable references and variable declarations are not set in the function scope.
- Parsing is about twice as fast as the eager parser
- Identify a limited number of errors (not following JavaScript specifications)
Lazy or eager?
Lazy precompilation is determined by the first two letters; So if we want to skip lazy and trigger eager compilation, we should prefix it with a bit operator, such as ‘! | ~ ‘. Let’s go straight to the code:
let a = 0; IIFE = Immediately Invoked Function Expression (Function eager() {... }) (); // the function body is lazy // the top-level function is not lazy() {... } // The body of the function is lazy // subsequent execution... lazy(); // ->eager starts parsing and compiling!Copy the code
// The eager resolution is triggered in this way. function eager2() {... }, function eager3() {... } // All eager // error! let f2 = function lazy() {... } (); // Lazy resolution is triggered and eager resolution is triggeredCopy the code
Why are Lazy and Eager both important?
- We need a lazy parser because web pages use a lot of extraneous code; (Fact, waving hands)
-
How to choose?
- If we eager parses our irrelevant code, we’re wasting time;
- If we lazy parsed our relevant code, we would overpay for pre-parsed time: 0.5 x parsed time + 1 x parsed time = 1.5 parsing time
- Let’s say we only know our startup code, and we don’t know exactly what code will be executed. (Fact again, waving hands)
Eager resolution is forced
- Optimize.js wraps parentheses around the functions it thinks will be executed.
The browser | With optimize-JS, startup speeds generally improve |
---|---|
Chrome 55 | 20.63% |
Edge 14 | 13.52% |
Firefox 50 | 8.26% |
Safari 10 | – 1.04% |
-
In fact, we just need to
- Parse correctly compiled functions;
- Minimize the cost of our failure;
- Iterate on this basis
How can Web developers take advantage of V8’s parser?
Use less code!
- JavaScript startup performance;
- Use less JavaSCript: Use the Code Coverage feature of Chrome Dev Tools;
- Measure your code parsing overhead:
chrome://tracing
和v8.runtime_stats
Code caching + Bundling
- Code caching: V8 caches frequently used JavaScript bytecode;
- Bundling: If you update part of the bundle’s code, you will lose the entire bundle cache.
- Avoid the use of
eval
Web developers: Use streaming
- Streaming JavaScript: parallel download and parsing;
-
Bulky JavaScripts
- Asynchronously read as early as possible;
- Make sure streaming JavaScript works
chrome://tracing
Bracket Black magic
-
Use the parenthesis technique to select the critical path that you want eager to parse and compile:
- Older versions of Chrome;
- Cross-browser;
- We need to improve performance now, now! (Can’t wait for us to fix it)
Additional content
- The V8 parser is a V8 recursive descent compiler;
- Approximately ~ 15K lines of C++ and ~ 7K lines of C for the AST+Scopes