Reprint please indicate the source: Http://blog.csdn.net/horkychen
The V8 JavaScript engine developed by Google has excellent performance. We ask the author who is familiar with the internal program implementation Yi Yuan code to see how the V8 accelerates.
Author: Community Engine Company Research and development engineer Hajime Morita
The V8 JavaScript engine in Google's Chrome has attracted considerable attention due to its good performance. It was developed by Google specifically for Chrome to run Web Apps (WEBAPP) at high speed. Chrome uses the Apple-led WebKit development program as the rendering engine (Rendering engine). WebKit is also used in the Safari browser. The WebKit standard is equipped with a JavaScript engine called JavaScriptCore, but Chrome is replaced by V8 (Figure 1).
V8 Development Group is a group of program language experts. The core engineer, Lars Bak, developed hotspot, an accelerated technology for Java Virtual Machines (VMS) developed in Sun Microsystems. He also developed an experimental Smalltalk system called Strongtalk in the Animorphic Systems company in the United States (which was acquired by Sun Microsystems in 1997). V8 has fully developed the knowledge gained in the development of hotspot and Strongtalk.
Figure 1 Developing your own JavaScript engine Apple's Safari and Google's Chrome use the same rendering engine. The WebKit rendering engine with JavaScriptCore is standard in JavaScript engine, but is replaced by V8 in Chrome.
requirements for high speed engines
The Google research team started developing V8 in 2006, partly because Google was dissatisfied with the speed with which the JavaScript engine was executed. I think the JavaScript engine was slow for two reasons: the historical background of development, and the complexity of the JavaScript language.
JavaScript has existed for at least 10 years. In 1995, it appeared in Netscape Navigator 2.0, a Web browser developed by Netscape (Netscape Communications). For a while, however, people are less demanding on performance because it is used only on a few animations, interactions, or other similar actions on a Web page. (The most obvious is to reduce network transmission to improve efficiency and improve interactivity.) The display speed of the browser depends on the speed of the network transmission and the speed of the rendering engine (rendering engine) parsing HTML, CSS (cascading style sheets, CSS), and other code. The development work of the browser gives priority to the speed of the rendering engine, while the processing speed of JavaScript is not too important. Java, which appears at the same time, has made quite a lot of progress, and it is being done more and more quickly to compete with C + +.
However, in the past few years, JavaScript has suddenly been widely used. The reason is that software that was previously used as a desktop application, including Office suites, is now a software that can be executed in a browser.
Google itself has launched several JavaScript web apps, including its Gmail email service, Google Maps map data service, and Google Docs Office suite.
The speed of these applications is influenced not only by servers, networks, rendering engines, and many other factors, but also by the speed with which JavaScript itself executes. However, the existing JavaScript engine can not meet the new requirements, and poor performance has always been the network application developers most concerned.
the problem of the language itself
The specification of the JavaScript language is now a huge performance strain. This is quite obvious, for example, when it determines the variable type. Mainstream languages, such as C + + and Java, use static typing. The variable type is declared when the code is compiled. Static types occupy a performance advantage because they do not need to be checked for data types during execution.
In general processing systems such as C + + and Java, the contents of fields* and methods* are stored as arrays, with 1:1 offset (offset) corresponding to the names of fields and methods (Figure 2). Stored locations, such as individual variables and methods, are defined for each class. In languages such as C + + and Java, the type of variable (class) accessed is known in advance, so the language interpretation system (interpreting systems) uses arrays and displacements to access field and method. The displacement enables it to access field, locate field, or perform other tasks as long as it has several machine language instructions.
Figure 2 JavaScript and C + +, Java differences C + +, Java, and other processing systems store the fields and methods, in their names, in an array with the displacement values in the 1:1 corresponding array. The type of variable (class) to be accessed is known in advance, so you can access fields and methods with only arrays and displacements. In JavaScript, however, individual objects have tables of their own properties and methods. Each time a program accesses a property or a call method, it must examine the object's type and perform the appropriate processing.
* Field: A variable of a genus object. C + + is called the member variable.
* Method: The processing type of the genus object. C + + is called a member function.
* Property Attribute: JavaScript property is a variable owned by the object itself. In JavaScript, a property can be not just a standard value, but also a methods.
* Hash Table Hash Table: A data structure that returns the corresponding value associated with a particular key. It has an internal array that uses the hash value generated by the key value as the displacement of the manifest value for a particular position in the array. If a different key hash value is generated just in the same location, the manifest location stores multiple values, which means that the hash value must be checked before any values are returned.
On the other hand, JavaScript uses dynamic typing. The JavaScript variable has no type, and the type of the specified object is determined at the first execution (in other words, dynamically). Each time you access a property in JavaScript, or seek a method, you must check the type of object and follow it.
Many JavaScript engines use hash tables (hash table) to access properties and find methods. In other words, each time an attribute is accessed or a method is searched, a string is used as the key (Figure 3) to find the object's hash table.
Figure 3 The internal JavaScript processing of property access uses the object x hash table string "foo" as a keyword for searching "foo" content.
The Search hash table is a sequential action that contains the position within the array from the hash (hashing) value, and then see if the key value (key) in that position is story. You can then use the displacement to read the data directly from the array comparison, the use of this method to access more time-consuming.
Other languages that use dynamic types, as well as Smalltalk and Ruby. These languages are basically search hash tables, but they use classes to shorten the search time. However, JavaScript does not have classes. In addition to "numbers" indicating numeric values, "strings" as strings, and a few other types, other objects are "object" types. A programmer cannot declare a type (class), and therefore cannot use an explicit type to expedite processing.
The flexibility of JavaScript allows you to add or delete properties and methods on objects at any time (see appendix). The JavaScript language is very dynamic, and the industry's general view is that dynamic languages are more difficult to accelerate than static languages such as C + + or java. In spite of the difficulties, V8 uses several techniques to achieve the goal of acceleration:
1.JIT Compilation (JIT Compile)
Generate machine language without byte code (bytecode)
From a performance point of view, V8 has 4 main features. First, it produces the machine language in its execution, called Just-in-time (Just-in-time, JIT) compilation. This is a common way to improve the speed of interpretation, and this method can be found in languages such as Java and. Net. V8 has practiced this technology earlier than the SpiderMonkey JavaScript engine in Firefox, or competitive engines like Safari's JavaScriptCore.
The V8 JIT compiler does not produce an intermediate code when it produces a machine language (Figure 4). For example, in the Java compiler, the original code is first converted into a class file (class file) represented bya virtual intermediate language (called bytecode, bytecode). The Java compiler and bytecode compiler produce bytecode, not machine language. The Java VM interprets the bytecode sequentially in execution. This execution pattern is called the bytecode interpreter (bytecode interpreter). Firefox's SpiderMonkey has an internal bytecode compiler and a byte interpreter that converts the JavaScript source code into its own characteristic byte code for execution.
Figure 4 V8 JIT compiler direct output machine language The system uses the parser to convert the original code into an abstract syntax tree (abstract syntax trees, AST). There are several ways to deal with them before. The bytecode compiler compiles the abstract syntax tree into intermediate code and executes it in the compiler. Mixed patterns such as Java JIT compile part of this intermediate code into machine language to improve processing performance. Chrome does not use intermediate code, JIT compiles machine language directly from the abstract syntax tree. There are also abstract syntax tree interpreters that parse the abstract syntax tree directly.
In fact, the Java VM currently uses a JIT compiler based on hotspot. It acts as the role of the bytecode interpreter to parse the code, converting the frequently executed code blocks into machine language and then executing, which is the blending mode (hybrid model).
Bytecode interpreters, blending modes, and so on, have the advantages of simple fabrication and excellent portability. As long as it is the source code that the engine can compile, it is possible to execute bytecode on any CPU architecture, which is why the technology is called a "virtual machine (VM)". Even in a mixed mode of generating machine code, you can start with the interpreter that writes the bytecode and then implement the machine language builder. By using simple bit code, it is much easier to optimize output when machine code is generated.
Instead of converting the original program into an intermediate language, V8 generates the machine language directly from the abstract syntax and executes it. There is no virtual machine, and because the intermediate representation is not required, program processing starts earlier. However, on the other hand, it also loses the benefits of virtual machines, such as the High Portability (portability) and simplicity of optimizations that are brought about through bytecode interpreters and blending modes.
2. Garbage Collection Management
subtle implementations of Java standard features
The second key feature is that V8 garbage collection management (garbage collection, gc*) as a "precise gc*". " Conversely, most JavaScript engines, Ruby, and other language compilers use a conservative gc* (Conservative GC) because conservative GC implementation is much simpler. Although the precise GC is more complex, it has a performance advantage. Oracle (Sun) Java VM uses an exact GC.
* Garbage collection (GC) garbage collection Management: automatically detects and releases memory space that is reserved by the program but is no longer in use.
* Conservative (Conservative) GC: There is no separate strict management of the indicator and the digital value of the memory recovery management. This approach is if it can be an indicator, look at it as an indicator, even if it's possible to count. This method prevents an object from being accidentally reclaimed, but it also fails to release possible storage.
Although the precise GC itself is efficient, advanced algorithms based on precise GC, such as the generational GC, replication (copy) GC, and tagging and streamlining processing (mark-and-compact processing), are significantly improved in performance. The generational GC improves GC efficiency by separating the management of "young generational" objects (often collected) and "older generational" objects (objects that are relatively long-lived).
V8 uses a generational (generational) GC to replicate a GC on a new generational (generational) process using a slight (light-load) replication, and a token and a thin GC on the old GC because it has to move objects within the memory space. This is difficult to implement in a conservative GC. In the replication of objects, when compressed (compaction) (called Defrag on the hard disk) and similar actions, the address of the object changes, and for this reason, the most common method is to use the "handle" (handles) to refer indirectly to the address. However, instead of using a handle (handles), V8 overrides all the data referenced by that object. Not using a handle (handles) can make implementation more difficult, but it can improve performance because of the lack of indirect references. The Java VM hotspot also uses the same technology.
3. Embedded caching (inline cache)
not available in JavaScript.
V8 can now produce a suitable machine language for x86 and arm architectures. Although not in C + + or Java traditional optimization methods, V8 still have dynamic language and the inherent speed.
A good example of this is the embedded cache (inline cache), which avoids hash-table searches when method calls and attribute accesses are available. It can immediately cache previous search results and is therefore called "inline." The technology has been known for some time and has been used in languages such as Smalltalk, Java, and Ruby.
Inline caching assumes that objects are of type, but not in the JavaScript language. This is why the previous JavaScript engine did not have an inline cache until V8 appeared.
To break through this limitation, V8 analyzes the program action at execution time and uses the "Hidden Class" (hidden classes) to specify a temporary class for the object. With hidden classes, even JavaScript can use an inline cache. But these classes are techniques to improve execution speed, not an extension of the language specification. So they can't be referenced in JavaScript code.
4. Hidden class
Storage type conversion Information
Hidden classes Create interesting challenges for JavaScript language specifications that do not have classes, and are also the most unique techniques used by V8 to promote speed. They deserve to be explored in greater depth.
There are two main reasons for creating classes in V8, that is, (1) classifying objects with the same property name, and (2) identifying objects with different attribute names. Objects in the previous class have exactly the same description of the object, which can speed up property access.
In V8, classes that qualify for a collation are configured on a variety of JavaScript objects. Object references the class that is configured (Figure 5). However, these classes exist only in V8 as a convenience, so they are "hidden".
Figure 5 V8 object has a reference to a hidden class if the description of the object is the same, the hidden class will be the same. In this example, both the object P and Q belong to the same hidden class
I mentioned above that you can add or remove attributes from JavaScript at any time. However, when this happens, the collation condition (the attribute with the same generalization name) is destroyed. V8 is resolved by the new class required to establish a property change. The object that the property changes is incorporated into the new level through a program called type conversion (class transition).
The second goal-identifying objects with different attribute names-is achieved by creating a new class. However, if a new class is created for each property change, it is not possible to consistently achieve the first goal (inductive attribute with the same name).
Figure 6 Configuring the new class: the object to which the type conversion attribute is changed is grouped into the new class. When the object P adds a new property z, the object p is grouped into the new class.
V8 stores the transformation information in a class to resolve this problem. Consider Figure 7, which illustrates the scenario shown in Figure 6, where the new attribute x is added to the point-level object p when the hidden class point has the X and Y properties. When the new property Z is added to the object p, V8 stores the information "new property p, building Point2 class" in the point-level internal table (Figure 7, Step 1).
Figure 7 Storing class transform information in a class when you add a new property z to an object p, V8 records "add attribute Z" to the table in the point class, creating a Class point2"(step 1). When object Q of the same point class joins attribute Z, V8 searches for the Point class table first. If it finds that the Point2 class has joined Property Z, the object q is set to the Point2 class (step 2).
When the new attribute z is added to the point-level object Q, V8 first searches the point-level table and discovers that the Point2 level has joined attribute Z. When a class is found in a table, the object q is set to the class (Point2) without creating a new class (Figure 7, Step 2). This achieves the purpose of generalizing the object with the same attribute name.
This method, however, means that the empty object corresponding to the hidden class will have a large conversion table. V8 is handled by creating hidden classes for each constructor. If the constructor is different, even if the object's statement (layout) is identical, a new hidden class is created for it.
Inline caching (Inline cache)
Unlike other JavaScript engines and V8, they store object properties in a hash table, but V8 store them in an array. Displacement information-Specifies the position of the individual property in the array-is stored in the hash table of the hidden class. Objects of the same hidden class have the same property name. If you know the object class, you can use the displacement to access the property according to the array operation. This is much faster than searching a hash table.
However, in dynamic languages such as JavaScript, it is difficult to know the object type in advance. For example, the source code for Figure 8 is the object type P and Q call lengthsquared () function. The properties of the object type P and Q are different and the hidden classes are different. Therefore, the parameter (arguments) type of the lengthsquared () function code cannot be determined.
To read an object property in a function, you must first check the hidden class of the object and have a hash table of the search class to find the displacement of the property. And then use the displacement to access the array. While accessing attributes in an array, the need to search the hash table first destroys the advantage of using the array.
However, from different points of view, the situation varies. In real-world programs, there is not much that relies on the type of code execution judgment. For example, the lengthsquared () function in Figure 8 even assumes that most of the values passed as arguments are point class objects, which are generally correct.
function lengthsquared (p) {return
p.x* p.x+ p.y* p.y;
}
function labeledlocation (name, x, y) {
this.name= name;
this.x= x;
This.y= y;
}
var p= new Point (a);
var q= new labeledlocation ("Hello", m);
var plen= lengthsquared (p);
var qlen= lengthsquared (q);
Figure 8 Code sample: JavaScript cannot determine that a function parameter type cannot determine the labeledlocation type of a point type or lengthsquared () function before executing it.
Inline caching is an accelerated technique that is designed to take advantage of partial (local) classes in programs. To programmatically attribute access, V8 produces a sequence of commands to search for a hidden class list (Figure 9). This code is called the premonomorphic stub. This stub is for the function Access property (Figure 10). The premonomorphic stub has two messages: a hidden class for searching, and a hidden displacement. Finally, new code is generated to cache this information (Figure 11).
object* find_x_for_p_premorphic (object* p) {
class* klass= p->get_class ();
int offset = Klass->lookup_offset ("x");
Update_cache (Klass, offset);
Return p->properties[offset];
}