Optimization of the code begins not so much with the study of the features of a programming language, but with an understanding of the scheme of operation of the whole "technological chain" involved in creating an application - from the program's algorithm to the compiler.
We spoke with
Vyacheslav Egorov aka
mraleph , an engineer from Google, a compiler to the core, who worked on a JavaScript engine called V8, built into Chromium (and, as a result, in Chrome, the Android browser version, the cloud-based operating system Chrome OS) and in less famous maxthone.
JavaScript programmers Vyacheslav, most likely, is known as the author of
posts about the insides of V8 and as a speaker, enthusiastically showing machine code at conferences for Web developers.
Currently, Vyacheslav is actively working on Google on Dart VM.
In this interview, he talked about what was going on inside the engine that runs dynamic JS code and shared examples of how some optimizations are performed and why it is important to understand the engine’s work in depth to ensure fast code execution.
')
The V8 engine was developed by the Danish division of Google and distributed under a BSD license. The engine is written in C ++ and supports the ECMA-262 specification.
The first version of the engine appeared in 2008. Currently, the active development of the engine continues for the most part in the Munich office of Google.
Among the main features of the engine are javascript compilation directly into machine code, adaptive optimization and de-optimization of the code at compile time, quick garbage collection.
- Please tell us in a few words about yourself and about working at Google related to the V8 JavaScript engine?- Finished mekhmat NGU. Always interested in compilers. He first worked at Excelsior Novosibirsk, where people make their own JVM with an AOT compiler, then went to Google. At first, Google was engaged in V8, then Dart VM, for some time even fixed various bugs in LuaJIT.
- Many people prefer the V8 car for its ability to optimize the code. What's the secret?- The main difficulty in optimizing dynamic programming languages ​​is that in order to achieve peak performance, the virtual machine must, in essence, guess the programmer’s intention and see the static structure hidden in the dynamic code. A strong feature of the V8 is that it can notice well enough this very structure.
For example, someone wrote in JavaScript:
function len(p) { return Math.sqrt(px * px + py * py); }
and it turns out that V8 is quite capable of compiling the body of this function into a fairly compact machine code:
vmovsd xmm1,[rax+0x17] vmulsd xmm1,xmm1,xmm1 vmovsd xmm2,[rax+0x1f] vmulsd xmm2,xmm2,xmm2 vaddsd xmm1,xmm2,xmm1 vsqrtsd xmm1,xmm1,xmm1
This is a completely non-trivial task in conditions where everything is dynamically typed and statically completely incomprehensible what
px
or even
Math.sqrt
.
- What about the weaknesses of the engine?- Sometimes the strong side of the V8 becomes its weak side. This happens for two reasons.
Firstly, not in any dynamically typed code, V8 is able to see a static structure, which, perhaps, was obvious to the programmer who wrote this code. Somewhere this happens, because in V8 something else is not implemented, somewhere - because it is not always algorithmically possible.
One common example is the code in the style:
function make(x) { return { f: function () { return x } } } var a = make(0), b = make(0);
Here, V8 cannot notice that
af and
bf have the same behavior (adjusted for the value of the captured variables), because these are functions created from the same functional literal (function literal).
Secondly, sometimes the V8 optimizing compiler simply does not support some construction in the code, and therefore the compiler refuses to look at the code. For example, Crankshaft (this is the first implementation of the idea of ​​adaptive compilation in V8) never supported try-catch / finally and therefore refused to optimize the functions where it was present. Now functions that use try-catch are compiled via TurboFan (a compiler developed to replace Crankshaft), so the situation is rectified.
Thirdly, sometimes the costs spent on identifying a static structure do not pay off. V8 spends time, builds trees of hidden classes, optimizes / deoptimizes / optimizes the code again - and the code doesn't get better, for example, because objects are mostly used as dictionaries. This is a very interesting problem area - how to properly balance the costs of code optimization and performance improvement from this optimization. How to execute code at a reasonable speed, even if this code does not fall on the obvious fast path.
Work on all these problems is underway, and therefore, in any cases when your code is slow on V8, you should complain to the V8 developers - at least so that they explain why the code is slow or maybe even fix something in the V8 itself .
By the way, everything said above to one degree or another applies to any JavaScript engine that at least somehow tries to optimize your code. All engines try to guess what the programmer wanted to say with his program, and try to see the static in the dynamic. All engines have a division into fast path and slow path and their own features.
- Do you think it makes sense to optimize the code for a specific engine (in our case - V8)? After all, it happens that in some browser the code slows down. What to do then?- There are two possible options (or a combination of them):
- your code is “bad.” In this case, performance under just several engines usually suffers, and optimization under one engine improves code performance right under several;
- engine code is "bad." In this case, as I noted earlier, you need to send a bug report to the developers of the engine, who can either fix the engine itself, or often recommend a way around the bug.
- Can you list the most common “rakes” that developers are attacking in their code (i.e., in fact, the most common errors in the code that have a strong effect on performance when targeting V8 - within the framework of the above situation ” Is the code bad?)?- There are two types of rake. The first type is an algorithmic rake. For example, sometimes people iterate over a string using a loop in the style:
for (; s != ""; s = s.substring(1))
Many compilers begin to bounce slightly on the chair and rub their hands when they see such code, since implementing an optimization that would make such a cycle into something sane is a terribly interesting task. However, from the point of view of the developer, it is much more efficient to understand how much s.substring costs at best and worst, and not to write such code. Because without tricky optimizations inside the engine, this cycle has a time complexity O (n
2 ).
Another type of rake is the rake associated with the optimization of dynamic languages ​​(I have already touched on this above). For example, a polymorphic code, i.e. code that works with different types of objects. Such a code for V8 is often kryptonite (a crystalline radioactive substance appearing in the DC Comics universe. Kryptonite is famous for being the only nonmagical weakness of Superman and other Krypton people - it can have an effect on them that varies depending on the color of the mineral. -
note . ed. ) for Superman. The topic is quite deep, and I have
a whole post with pictures on this topic.
- How to deal with these problems? Search for “rules for writing code for a specific engine” and check your code for compliance with them?- The most important thing here is to realize that performance problems cannot be solved in a swoop. Suppose you heard about polymorphism and ran, losing slippers, rewriting all your code in a monomorphic style. Anything good from such undertakings usually does not work. Here we need a completely different approach, which is very well described by the classic Russian saying “measure seven times, cut once”. You need to build in your head a mental model of how a VM works and how your code works, you need to thoroughly profile, understand where your performance leaks, and only then ask yourself questions of optimization. It often turns out that the V8 does its job well, but the real bottleneck is not even in javascript code.
- And if you go back to the problems of the compiler code you mentioned (the situation “engine code is bad”)? Is it possible to bring some of the most frequently mentioned V8 problems?- Bugs do not usually appear "often" or "not often." It usually happens that the bug was noticed by some developer, it was quickly repaired, and that was it. Bugs at the same time affect only a small population of developers, because they are found in very special places with the right set of circumstances.
As an example from practice: I was once asked to look at the code, which for some reason sometimes started to work very slowly. It turned out all because of the expression
Math.floor (x * y) , in which sometimes x became equal to -1, and y equal to 0. It would seem nothing special, but
Math.floor (x * y) in this case equals the magic number “ negative zero ”(which is almost completely like 0, but if you divide, say, 1 by zero, you get positive infinity, and if you divide 1 by minus zero, you get negative infinity). Crankshaft, on the other hand, always assumed that the result of the
floor operation is an integer number, and therefore
-0 is a number that cannot be represented as a whole, which caused deoptimization. The solution to the problem in this particular case was to replace
Math.floor (x * y) with (x * y) | 0 (in general, this is not an equivalent conversion, but for the code that needed to be overclocked, it did not matter). By the way, recently this problem was
repaired in Crankshaft
once and for all .
I specifically chose this bug as an example, since he is quite mysterious (“what is the minus-zero?”, “what kind of de-optimization?”) in the hope of convincing the reader that knowledge of specific bugs is completely useless. I found that the code I was looking at was attacking this bug not because I knew that
Math.floor does not tolerate
-0 , and armed with this knowledge went to replace all of
Math.floor with
| 0 , until the bug was fixed ... No, I found this bug, because I knew how to profile the V8, with which keys I had to run it, so that the V8 showed me the list of de-optimizations. Therefore, it is most important to understand how V8 (and other JS VMs) work.
With this knowledge, you can always find out “what went wrong somewhere in deep dungeons,” and then complain to higher authorities.
I write a lot about this in my
blog and even
made a tool that allows you to view information issued by V8 (compiler IR, deopts, etc) in a more or less convenient form.
- You are now engaged in Dart? When will this new language replace js? Does the developer need to move to a new language now? What is his position in the industry?- Take and that's just so replace the JS in our whole universe is impossible. But in some jobs - it is quite possible. People replace it with different things, someone ClojureScript, someone TypeScript, and someone Dart. Programming languages ​​are complex and conflicting. Everyone has their opinion on their account. So my advice is usually simple - tired of javascript? You can go to dartlang.org, download the SDK (or
play with the language directly in the browser) and decide for yourself.
Of the interesting things that now occur with the language outside the Web, you can select
flutter.io - this is a framework for developing cross-platform (Android & iOS) mobile applications and
dartino - a small Dart for embedded systems.
Thank you for the conversation!
The examples above illustrate vividly that it’s not enough just to list the “simple recommendations for a specific engine” to optimize the code - it is important to understand the principle of its operation and, at the same time, to clearly understand what results you want to achieve from the program.
On June 5, in St. Petersburg, Vyacheslav will speak at the JavaScript conference of
HolyJS with a
report on JavaScript performance . Come!