A few weeks ago, we started a series of articles that delve into JavaScript and how it actually works, and we think that by understanding the building blocks of JavaScript and how they work together, you’ll be able to write better code and applications.
An overview of
A JavaScript engine is a program or interpreter that executes JavaScript code. A JavaScript engine can be understood as a standard interpreter, or a runtime compiler, which compiles JavaScript into bytecode in some form.
- V8Open source software developed by Google, written in C ++
- Rhino–Managed by the Mozilla Foundation, it is open source and developed entirely in Java
- SpiderMonkeyThe first JavaScript engine to support Netscape Navigator, now supports Firefox
- JavaScriptCore-Open source, sold as Nitro, developed by Apple for Safari
-
Kjs-kde engine, originally developed by Harri Porten for the KDE project’s Konqueror Web browser
-
Chakra (JScript9)-
Internet Explorer -
Chakra (JavaScript)- Microsoft Edge
-
Nashorn-Java language and tool set by OracleOpen source as part of OpenJDK
-
JerryScript— a lightweight engine for the Internet of Things
Why was V8 created?
V8 was originally designed to improve the performance of JavaScript executed inside web browsers, and to increase execution speed, V8 does not convert JavaScript code into more efficient machine code, rather than using an interpreter. Like many modern JavaScript engines, such as SpiderMonkey or Rhino (Mozilla), it compiles JavaScript code into machine code by implementing a JIT (just-in-time) compiler. The main difference here is that V8 does not generate bytecode or any intermediate code.
V8 used to have two compilers
Prior to V8 version 5.9 (released earlier this year), the engine used two compilers:
- Full-codegen — a simple and fast compiler that generates simple but unoptimized machine code.
- Crankshaft – a more sophisticated (just-in-time) optimization compiler that generates highly optimized code.
Multiple threads are also used in V8:
-
Main thread: Gets the code, compiles the code, and executes it
-
There is also a separate thread that is used to compile, so the main thread can continue to execute while it optimizes the code
-
An analysis thread that tells the runtime which methods are consuming a lot of time so that
CrankshaftYou can optimize them -
Some threads handle the scanning garbage collector
When the JavaScript code is first executed, V8 leverages full-CodeGen to convert the parsed JavaScript directly into machine code without any conversion in the middle of the process. This allows it to start executing machine code very quickly. Note that V8 does not use intermediate bytecode representation, so no interpreter is required.
After your code has been running for a while, the profiling thread has collected enough data to tell you which method to optimize.
Next, the Crankshaft optimization started with another thread that turned the JavaScript abstract syntax tree into an advanced static singleton assignment (SSA) representation named Hydrogen and attempted to optimize the Hydrogen chart, where most of the optimizations were done.
inline
Hidden classes
JavaScript is a prototype-based language, it does not create classes, objects are created by reference, and JavaScript is also a dynamic programming language, which means you can easily add or remove properties from objects after instantiation.
Most JavaScript parsers use dictionary-like structures (based on hash functions) to store the in-memory location of object attribute values. This structure makes retrieving property values in JavaScript more computationally expensive than in non-dynamic programming languages such as Java or C#. In Java, All object properties are determined by a fixed object template before compilation, and unable to dynamically add or delete at runtime (c # dynamic type, this is another topic), as a result, the attribute value (or pointer to these attributes) can be used as a buffer in memory in a row, has a fixed offset between each buffer, The length of the offset can be easily determined based on the attribute type. This is not possible in JavaScript, where property values can be changed at run time.
Since it is inefficient to use dictionary structures to locate attribute values in memory, V8 uses a different approach instead: hiding classes. Hidden Classes act like fixed object templates in the Java language, unless they are created at run time. Let’s see what they actually look like:
function Point(x, y) {
this.x = x;
this.y = y;
}var p1 = new Point(1, 2);Copy the code
Once theNew Point (1, 2)
The call occurs, and V8 creates a hidden class named “C0”.
No attributes have been defined for Point, so “C0” is null.
Once the first line of this.x = x is executed (inside the Point method), V8 will create a second hidden class “C1” based on “C0”. “C1” describes where in memory the attribute x can be found (relative to the object pointer). This means that when a Point object in memory is treated as a continuous buffer, the first offset position will correspond to the attribute “X”. V8 will also update “C0” with “class conversion”, which means that if attribute “x” is added to a Point object, the hidden class should switch from “C0” to “C1”. The hidden class of the Point object below is now “C1”.
Each time a new property is added to an object, the old hidden class is updated to the transformation path pointing to the new hidden class. Hidden class conversions are important because they allow hidden classes to be shared between objects created in the same way (such as instantiating two Point objects whose common hidden class is C0). If two objects share a hidden class and the same attribute is added to them, the transformation will ensure that both objects receive the same new hidden class (such as adding an "X" attribute, which will both point to C1) and all optimization code
This process is repeated when “this.y=y” is executed (” this.y=y “in the Point function). If the attribute” y “is added to the Point, the class conversion will generate” C2 “hidden classes based on” C1 “, and the hidden classes of the Point object will be updated to “C2”.
Hidden class conversions depend on the order in which attributes are added to the object. Look at the following code:
functionPoint(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;Copy the code
Inline cache
Another way V8 optimizes the dynamically typed language is called inline caching, which relies on the observation that repeated calls to the same method tend to occur on objects of the same type. An in-depth explanation of inline caching can be found here.
So how does it work?
What about the concept of how hidden classes and inline caching are related?
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 if the two objects are of the same type, their corresponding hidden classes will assign different offsets to their attributes.
The two objects are essentially the same, but the "A" and "b" properties are created in different order.
Compile to machine code
The garbage collection
For garbage collection, V8 uses the traditional generational tag removal garbage collection mechanism to remove the older generation. JavaScript stops executing during the tag phase. In order to control the cost of GARBAGE collection and the stability of code execution, V8 uses incremental tags: And traverse the entire heap, trying to mark every possible object is different, it’s just part of the tag heap, and then resume normal execution, the next time the GC will continue to traverse, from the place where the last stop in the execution time periods, it allows a short pause, as previously mentioned, the sweep phase in a separate thread.
With the release of version 5.9 of V8 earlier in 2017, a new execution pipeline was introduced that achieved even greater performance gains and significant memory savings in actual JavaScript referencing programs.
The new pipeline is built on TOP of V8’s interpreter Ignition and TurboFan, V8’s latest optimized compiler,
You can check out the V8 team’s blog post on the subject here.
Since the release of version 5.9 of V8, as the V8 team struggled to keep up with the new JavaScript language features and the optimizations required for them, Full-codegen and Crankshaft (which have been serving V8 since 2010) are no longer used by V8 to run JavaScript.
This means that V8 as a whole will have a simpler and more maintainable architecture.
Improvements to the Web and Node.js benchmarks
These optimizations are just the beginning, and the new Ignition and TurboFan pipelines pave the way for future optimizations that will allow even greater improvements in JavaScript performance and allow V8 to conserve resources in Chrome and Node.js.
Finally, here are some tips to help you write better, higher-quality JavaScript. You can certainly draw some tips easily from the above, but a summary is provided for your convenience.
How to write the best JavaScript code
1. Order of object properties: Always instantiate your object properties in the same order, so that hidden classes and subsequent optimization code can be shared.
2. Dynamic properties: Adding new properties to an object after its instantiation causes hidden class changes that slow down the execution of methods optimized for the old hidden class. Therefore, try to allocate all the attributes of the object in the constructor.
3. Methods: Code that executes the same method repeatedly will run faster than code that executes a different method only once (due to inline caching).
4. Arrays: Avoid Sparse Arrays with keys that aren’t increasing numbers. A sparse array that does not allocate memory for each element is essentially a hash table. The elements in this array are more expensive to fetch than the elements in a normal array. Also, avoid using large arrays of pre-requisition. It is best to increase the size of the array slowly as needed. Finally, do not delete elements in the array, as this will make keys sparse.
5. Token values: V8 uses 32 bits to represent objects and numbers. It uses one bit to distinguish between an object (flag = 1) and an Integer (flag = 0) (called SMI or SMall Integer because it has only 31 bits to represent the value). Then, if a number is greater than 31 bits, V8 boxing the number, turning it into a double, and creating a new object to put the double into. So, to avoid the costly Boxing operation, use a 31-bit signed number.
Follow-up document translation will follow up!!
Welcome to the xuan xuan front public account, the follow-up will launch a series of articles “a large graphical application 0 to 1 process”, this account will also be updated synchronously