JavaScript working mechanism: V8 engine internal mechanism and how to write optimized code 5 tips

Source: Internet
Author: User
Tags microsoft edge


Overview


The JavaScript engine is a program or interpreter that executes JavaScript code. The JavaScript engine can be implemented as a standard interpreter, or an instant compiler that compiles JavaScript into bytecode in some way.



Here is a list of popular items that implement the JavaScript engine:


    • v8-Open source, developed by Google, is written in C + +
    • Rhin O managed by Mozilla Foundation, Open source, fully developed in Java
    • spidermonkey-first JavaScript engine, originally used on Netscape Navigator, is now used in Firefox.
    • Javascriptcore-Open source, sold in Nitro, developed by Apple for Safari
    • Kjs-kde's engine was originally developed by Harri Porten, and the Konqueror browser for KDE projects
    • Chakra (JSCRIPT9)-internet Explorer
    • Chakra (JavaScript)-microsoft Edge
    • nashorn-Open Source is part of the OPENJDK, developed by Oracle's Java language and tools group
    • Jerryscript-is a lightweight engine for the internet of Things
The origin of creating the V8 engine


The V8 engine built by Google is open source and written in C + +. The engine is used in Google Chrome. However, unlike other engines, V8 is also used as a very popular runtime for node. js.






V8 was originally designed to improve the performance of JavaScript execution in a Web browser. To get the speed, V8 translates the JavaScript code into more efficient machine code rather than using an interpreter. It compiles JavaScript code into machine code by implementing a JIT (instant) compiler like many modern JavaScript engines (such as SpiderMonkey or Rhino). The main difference here is that V8 does not produce bytecode or any intermediate code.


V8 once had two compilers


Before the advent of version 5.9 of V8 (released earlier this year), the V8 engine used two compilers:


    • full-codegen– a simple and super-fast compiler that can generate simple and relatively slow machine code.
    • crankshaft– a more complex (immediate), optimized compiler that can generate highly optimized code.


The V8 engine also uses multiple threads internally:


    • The main thread executes the work we want it to do: Get the code, compile and execute it
    • There is also a separate thread for compiling, so that while the main thread continues to execute, separate threads can simultaneously optimize the code
    • A profiler thread that lets the runtime know which methods spend a lot of time, so that crankshaft can optimize them
    • Several threads for handling garbage collector sweeps


The first time the JavaScript code is executed, V8 uses Full-codegen to translate the parsed JavaScript directly into machine code without any conversion. This allows it to start executing the machine code very quickly. Note that because V8 does not use an intermediate bytecode representation, there is no need for an interpreter.



After the code has been running for some time, the profiler thread has collected enough data to determine which method should be optimized.



Next, crankshaft optimizations start from another thread. It translates the JavaScript abstract syntax tree into an advanced static single-assignment (SSA) representation called hydrogen, and attempts to optimize the hydrogen diagram. Most optimizations are done at this level.


Inline


The first optimization is to inline as much code as possible in advance. Inline is the process of replacing the calling location (the line of code where the function is called) with the function body of the called function. This simple step makes the following optimizations more meaningful.






Hide Class



JavaScript is a prototype-based language: It has no classes and objects are created with a cloning process. JavaScript is also a dynamic programming language, meaning that after an object is instantiated, you can arbitrarily add or remove properties to an object.



Most JavaScript interpreters use a dictionary-like structure (based on a hash function) to store the location of object property values in memory. This structure makes it more expensive to get the value of a property in JavaScript than in a non-dynamic programming language such as Java or C #. In Java, all object properties are determined by the pre-compilation fixed object layout and cannot be dynamically added or removed at run time (C # has a dynamic type, which is another topic). Therefore, the value of the property (or a pointer to these properties) can be stored in memory as a contiguous buffer, with a fixed offset between each buffer. The length of the offset can easily be determined by the property type. In JavaScript, this is not possible because the property type may change during run time.



Because it is very inefficient to use a dictionary to find the location of object properties in memory, V8 uses a different approach instead: hiding the class. Hiding a class works like a fixed object layout (class) used in a language like Java, except that hidden classes are created at run time. Below, 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 (1, 2) call occurs, V8 creates a hidden class called C0.






Because the attribute has not been defined for point, C0 is empty.



Once the first statement is executed this.x = x (in the point function), V8 creates a second hidden class C1 based on C0. C1 describes the location in memory (relative to the object pointer), where the attribute x can be found. At this point, x is stored at offset address 0, which means that when the in-memory point object is viewed as a continuous buffer, the first offset address corresponds to the attribute x. V8 also uses class conversion to update the C0, indicating that if you add a property x to the Point object, the hidden class should switch from C0 to C1. The hidden class for the following point object is now C1.






Whenever a new property is added to an object, the old hidden class is updated with a transform path to the new hidden class. It is important to hide class conversions because they allow hidden classes to be shared between objects that are created in the same way. If two objects share a hidden class, and the same attributes are added to both objects, the conversion ensures that two objects receive the same new hidden class and all the optimized code that comes with it.



This process is repeated when the execution statement this.y = Y (also after the this.x = x statement inside the point function).



At this point, a new hidden class named C2 is created, and the class conversion is added to C1, which means that if you add the property y to the Point object (which already contains attribute x), the hidden class should be changed to C2, and the hidden class of the point object is updated to C2.






The hidden class conversions depend on the order in which the attributes 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 might think that P1 and P2 will use the same hidden classes and transformations. Well, it's wrong. For P1, the first is to add property A, then attribute B. However, for P2, first assigns a value to B, then a. As a result, P1 and P2 will eventually have different hidden classes because of the different conversion paths. In this case, it is better to initialize the dynamic property in the same order so that the hidden class can be reused.


Inline cache


V8 uses another technique called inline caching (inline caching) to optimize the dynamic type language. Inline caching comes from the observed result: repeated calls to the same method often occur on objects of the same type. An in-depth explanation of the inline cache can be found here.



Let's talk about the general concept of inline caching (if you don't have time to read the in-depth explanation above).



So how does it work? V8 maintains the cache of object types passed as parameters in the most recent method call and uses that information to make assumptions about the types of objects that will be passed as parameters in the future. If V8 can make a good assumption about the object type passed to the method, it can bypass the process of working out how to access the object's properties, instead using the information stored when the object's hidden class was previously found.



So how does the concept of hidden classes and inline caches relate? Whenever a method is called on a particular object, the V8 engine must perform a lookup on the object's hidden class to determine the offset address that accesses a particular property. After two successful calls to the same method of the same hidden class, V8 omitted the hidden class lookup, adding only the offset address of the property to the object pointer itself. For all future calls to this method, the V8 engine assumes that the hidden class has not changed and jumps directly to the memory address of a particular property using the offset address stored in the previous lookup. This will greatly improve execution speed.



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, but use a different hidden class (as in the previous example), then V8 will not be able to use inline caching, because even if two objects have the same type, their corresponding hidden classes will assign different offset addresses for their properties.






Two objects are basically the same, but the "a" and "B" properties are created in different order.


Compile to machine code


Once the hydrogen diagram is optimized, crankshaft reduces it to a lower-level representation called lithium. Most lithium implementations are schema-oriented. Register allocations occur at this level.



Finally, lithium is compiled into machine code. Then other things, namely OSR (current stack substitution, On-stack replacement), have occurred. We might run it before we start compiling and optimizing a method that is obviously going to run for a long time. V8 is not stupid enough to forget the code it's just slowly executing, so it won't be executed again with the optimized version, but instead it will transform all the existing contexts (stacks, registers) so that we can switch to the optimized version in the middle of the execution process. This is a very complex task, keep in mind that, in addition to other optimizations, V8 has been inline with the code at the very beginning. V8 is not the only engine that can do this.



There is a protective measure called de-optimization that reverses the conversion and reverts to non-optimized code in case the engine's assumptions are no longer true.


Garbage collection


For garbage collection, V8 uses a traditional generational approach of marking and sweeping to clear the old generation. The tagging phase should stop executing JavaScript. To control GC costs and make execution more stable, V8 uses incremental markup: instead of traversing the entire heap, trying to tag every possible object, instead of just traversing a portion of the heap, and then resuming normal execution. The next GC stop will continue from where the previous heap traversal stopped. This allows for a very short pause during normal execution. As mentioned earlier, the sweep phase is handled by a separate thread.


Ignition and turbofan


With release 5.9 released earlier in 2017, V8 introduced a new execution pipeline. This new pipeline enables greater performance gains and significant memory savings in real-world JavaScript applications.



This new execution pipeline is based on the V8 interpreter ignition and V8 's latest optimizer compiler turbofan.



Here you can see the V8 team's blog post on this topic.



Since the release of version 5.9, V8 no longer executes JavaScript with Full-codeget and crankshaft (the technology used V8 since 2010) because the V8 team has struggled to keep up with the new JavaScript language features that need to be optimized.



This means that the overall next step of the V8 will be simpler and more maintainable architectures.






Elevation on Web and node. JS Benchmarks



These improvements are just the beginning. The new ignition and turbofan pipelines pave the way for further optimization, which will boost JavaScript performance over the next few years and reduce the share of V8 in chrome and node. js.



Finally, here are some tips on how to write well-optimized, better JavaScript. Of course, it is not difficult to get these tips from the above, but for the sake of convenience, here is a summary:



How to write an optimized javascript


    1. Order of Object properties: Always instantiate object properties in the same order so that hidden classes and subsequently optimized code can be shared.
    2. Dynamic properties: Adding properties to an object after instantiation forces the hidden class to be modified, slowing down the method optimized for the previously hidden class. Therefore, you should specify all the properties of the object in the constructor.
    3. Method: Code that repeats the same method will run faster than code that executes only once (due to inline caching).
    4. Arrays: Avoid sparse arrays of keys that are not incremental numbers. A sparse array of elements is a hash table, while accessing the elements in this array is more expensive. Also, try to avoid pre-allocating large arrays. It is better to grow with development. Finally, do not delete the elements in the array. It will let the key become sparse.
    5. Tagged Value: V8 represents objects and numbers with 32 bits. It uses one to determine whether the object (flag = 1) or integer (flag=0) (This integer is called the SMI (SMall Integer, small integer), because it is 31 bits). Then, if a number is greater than 31 bits, V8 will box the number, convert it to double, and create a new object to put the number inside. So use a 31-bit signed number as much as possible to avoid expensive boxing operations that convert to JS objects.


In Sessionstack we tried to follow these best practices in writing highly optimized JavaScript code. The reason is that once sessionstack is integrated into the product Web application, it begins to record everything: all DOM changes, user interactions, JavaScript exceptions, stack traces, failed network requests, and debug messages. With Sessionstack, you can replay issues from your web app into videos and see what happens to your users. And all of this happens without impacting the performance of your Web application.



JavaScript working mechanism: V8 engine internal mechanism and how to write optimized code 5 tips


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.