📜 ⬆️ ⬇️

Optimizing JavaScript performance for V8

Foreword


Daniel Clifford gave a great talk on Google I / O, dedicated to the features of optimizing JavaScript code for the V8 engine. Daniel urged us to strive for greater speed, carefully analyze the differences between C ++ and JavaScript, and write code, keeping in mind how the interpreter works. I have compiled in this article a summary of the most important points of Daniel's speech, and I will update it as the engine changes.

Top tip


It is very important to give any performance tips in context. Optimization often becomes an obsessive habit, and deep immersion in the wilds can actually distract from more important things. You need a holistic view of web application performance - before focusing on these optimization tips, you should analyze your code with tools like PageSpeed and first achieve a good overall result. This will help avoid premature optimization.

The best strategy for creating a fast web application is as follows:


To stick to this strategy, it is important to understand how V8 optimizes JS, to imagine how everything happens at run time. It is also important to have the right tools. In his speech, Daniel devoted more time to developer tools; In this article, I mainly look at the features of the V8 architecture.
')
So let's get started.

Hidden Classes


At compile time, the type information in JavaScript is very limited: at runtime, types can change, so it is only natural to expect that when compiling it is difficult to make assumptions about them. The question arises - how in such conditions can you at least get closer to the speed of C ++? However, V8 manages to create hidden classes for objects at runtime. Objects that have the same class share the same optimized code.

For example:

function Point(x, y) { this.x = x; this.y = y; } var p1 = new Point(11, 22); var p2 = new Point(33, 44); //    p1  p2         p2.z = 55; // !  p1  p2    ! 

As long as the “ .z ” property was not added to p2 , p1 and p2 inside the compiler had the same hidden class, and V8 could use the same optimized machine code for both objects. The less often you change the hidden class, the better the performance will be.

Findings:


Numbers


V8 keeps track of how you use variables, and uses the most efficient view for each type. Changing the type can be quite expensive, so try not to mix floating-point numbers and integers. In general, it is better to use integers.

For example:

 var i = 42; //  31-    var j = 4.2; //         

Findings:


Arrays


V8 uses two types of internal array representation:


Findings:


In this way:


Compiling javascript


Although JavaScript is a dynamic language and was originally interpreted, all modern engines are actually compilers. Two compilers work in V8 at once:


Basic compiler


In V8, he is the first to start processing all the code and run it as quickly as possible. The code generated by it is almost not optimized - the basic compiler makes almost no assumptions about types. During execution, the compiler uses inline caches, in which code-dependent portions of code are stored. When this code is run again, the compiler checks the types used in it before selecting the appropriate version of the ready-made code from the cache. Therefore, operators that can work with different types run slower.

Findings:


An operator is monomorphic if the hidden type of operands is always the same, and polymorphic if it can change. For example, the second call to add() makes the code polymorphic:

 function add(x, y) { return x + y; } add(1, 2); // +  add()  add("a", "b"); // +  add()   

Optimizing compiler


In parallel with the operation of the basic compiler, the optimizing compiler recompiles the “hot”, that is, those that are often executed, code segments. It uses type information accumulated in inline caches.

The optimizing compiler tries to build functions into the call sites, which speeds up execution (but increases memory consumption) and allows for additional optimizations. Monomorphic functions and constructors can easily be embedded entirely, and this is another reason why we should strive to use them.

You can see what exactly is optimized in your code using the standalone version of the d8 engine:

 d8 --trace-opt primes.js 
(the names of the optimized functions will be displayed in stdout )

Not all functions can be optimized. In particular, the optimizing compiler skips any functions containing try/catch blocks.

Findings:

If you need to use a try/catch , place performance-critical code outside. Example:

 function perf_sensitive() { //     } try { perf_sensitive() } catch (e) { //    } 

Perhaps the situation will change in the future, and we will be able to compile try/catch blocks with an optimizing compiler. You can see exactly which functions are ignored by specifying the --trace-bailout when running d8:

 d8 --trace-bailout primes.js 

Deoptimization


Code generated by the optimizing compiler is not always faster. In this case, the original, non-optimized version is used. Unsuccessfully optimized code is thrown away, and execution continues from the appropriate place in the code created by the base compiler. This code may be optimized again soon if circumstances permit. In particular, changing hidden classes inside an already optimized code leads to de-optimization.

Findings:


You can see exactly which functions are being deoptimized by running d8 with the --trace-deopt :

 d8 --trace-deopt primes.js 

Other V8 Tools


The above functions can be transferred to Google Chrome at startup:

 /Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --js-flags="--trace-opt --trace-deopt --trace-bailout 

There is a profiler in d8 too:

 d8 primes.js --prof 

The sampler profiler d8 takes pictures every millisecond and writes to v8.log .

Summary


It is important to understand how the V8 engine works in order to write well-optimized code. And do not forget about the general principles described at the beginning of the article:


This means that you have to make sure that it is in JavaScript, using tools such as PageSpeed. It may be worth getting rid of DOM calls before looking for bottlenecks. I hope that Daniel’s speech (and this article) will help you to better understand the work of V8, but do not forget that it is often more useful to optimize the algorithm of the program, rather than adjust to a specific engine.

References:


Source: https://habr.com/ru/post/154537/


All Articles