Big beard

Translation: the original huziketang.com/blog/posts/… 英文原文 : What makes WebAssembly fast?

Please indicate the source, keep the original link and author information


英文原文 : What makes WebAssembly fast?

This is the fifth article in the WebAssembly series (There are six articles in this series). If you haven’t read the previous article, suggestRead here. If you have no idea about WebAssembly, suggestRead here first.

In my last article, I covered how to write WebAssembly programs and expressed my hope that more developers will use both WebAssembly and JavaScript in their projects.

Developers don’t have to choose between WebAssembly and JavaScript. Developers who already have JavaScript projects want to try some of their JavaScript instead of WebAssembly.

For example, the team developing the React program could replace the modifier code, or virtual DOM, with the WebAssembly version. As for the users of your Web application, they will use it as before, nothing will change, and they will enjoy the benefits of WebAssembly – fast.

The reason developers choose to switch to WebAssembly is because WebAssembly is faster. So why is it fast? Let’s find out.

What is the current performance of JavaScript?

Before we look at the performance differences between JavaScript and WebAssembly, we need to understand how the JS engine works.

The graph below shows the rough distribution of performance usage.

The amount of time the JS engine spends on each part of the diagram depends on the JavaScript code used for the page. The proportions in the chart do not represent the exact proportions in the real situation.

Each color bar in the figure represents a different task:

  • Parsing — the time it takes to turn source code into code that the interpreter can run;
  • Compiling + Optimizing — represents the time spent on the baseline compiler and optimizing compiler. Some optimization compiler work that does not run on the main thread is not included here.
  • Re-optimizing — Time spent discarding optimization code when the JIT finds that optimization assumptions are incorrect. This includes the time to reoptimize, discard, and return to the baseline compiler.
  • Execution — The time that code is executed
  • Garbage collection – Garbage collection, the time it takes to clean up memory

Note here that these tasks are not performed discreetly, or in a fixed order. Instead, they interleaved execution, such as parsing while some other code was running and others were compiling.

Such cross-execution brought a big efficiency boost to early JavaScript implementations, which looked like the following, in order:

In the early days, JavaScript had only interpreters and was very slow to execute. When JIT is introduced, the execution efficiency is greatly improved and the execution time is shortened.

The cost of JIT is the monitoring and compilation time of the code. JavaScript developers can develop JavaScript programs just like before, and the same programs can be parsed and compiled in much shorter times. This makes developers more inclined to develop more complex JavaScript applications.

At the same time, it also shows that there is a lot of room for improvement in execution efficiency.

WebAssembly contrast

Here’s an approximate comparison of WebAssembly and a typical Web application:

Different browsers process the different processes shown above in slightly different ways, and I’m using SpiderMonkey as a model to illustrate the different stages:

File access

This step is not shown in the diagram, but what seems like a simple step to get a file from the server can take a long time.

WebAssembly has a higher compression rate than JavaScript, so file retrieval is faster. Even though compression algorithms can significantly reduce the package size of JavaScript, the compressed WebAssembly binary is still smaller.

This means that transferring files between server and client is faster, especially if the network is poor.

parsing

When it reaches the browser, the JavaScript source code is parsed into an abstract syntax tree.

Browsers take a lazy loading approach, parsing only what they really need, and leaving only piles of functions that the browser doesn’t need for the time being.

Once parsed, the AST (abstract syntax tree) becomes intermediate code (called bytecode), which is provided to the JS engine for compilation.

WebAssembly does not require this transformation because it is itself intermediate code. All it has to do is decode and check to make sure the code has no errors.

Compilation and optimization

As I mentioned in my last article on JIT, JavaScript is compiled at the execution stage of the code. Because it is weakly typed, the same code is compiled into different versions when the variable type changes.

Different browsers handle WebAssembly compilation differently, with some only doing baseline compilation of WebAssembly and others JIT compilation.

Either way, WebAssembly is closer to machine code, so it’s faster, and it’s faster for several reasons:

  1. It does not need to run the code ahead of time to know what types the variables are before compiling the optimized code.
  2. The compiler does not need to compile different versions of the same code.
  3. Many of the optimizations are already done in the LLVM phase, so there are not many optimizations that need to be done at compile and optimize time.

Heavy optimization

In some cases, the JIT will repeat the “discard optimized code <-> re-optimize” process.

This happens when the JIT makes assumptions during the optimization hypothesis phase that are found to be incorrect during the execution phase. For example, when a loop discovers that the variable type used in the current loop is different from the type used in the last loop, or when a new function is inserted in the prototype chain, the JIT will discard the optimized code.

The de-optimization process has two parts of overhead. First, it takes time to throw away the optimized code and go back to the baseline version. Second, if the function is still called frequently, the JIT may send it to the optimized compiler for another optimized compilation, which is a waste of time.

In WebAssembly, types are determined, so the JIT does not need to make optimization assumptions based on the type of a variable. That is, WebAssembly has no re-optimization phase.

perform

You can write efficient JavaScript code yourself. You need to understand JIT optimizations, such as what code compilers will treat it differently (discussed in the JIT article).

However, most developers are unaware of the JIT implementation mechanism. Even if a developer knows the mechanics of JIT, it can be difficult to write CODE that is JIT compliant because the coding patterns that people usually use to make code more readable are not the right ones for the compiler to optimize.

In addition, the JIT makes different optimizations for different browsers, so a good optimization for one browser is likely to perform poorly on another browser.

Because of this, WebAssembly execution is usually faster, and many of the optimizations that jits make for JavaScript are not needed in WebAssembly. In addition, WebAssembly is designed for compilers, so developers don’t program it directly, allowing WebAssembly to focus on providing more desirable instructions (ones that execute more efficiently) to the machine.

In terms of execution efficiency, different code functions have different effects, generally speaking, the execution efficiency can be improved by 10%-800%.

The garbage collection

In JavaScript, developers do not need to manually clean up unused variables in memory. The JS engine does this automatically, in a process called garbage collection.

However, when you want controllable performance, garbage collection can be a problem. The garbage collector will start automatically, which is out of your control, so chances are it will start at an inopportune time. Most browsers today already schedule garbage collection to start at a reasonable time, but this still adds overhead to code execution.

Currently, WebAssembly does not support garbage collection. Memory operations are manually controlled (like C and C++). This does increase development costs for developers, but it also makes the code more efficient to execute.

conclusion

WebAssembly executes faster than JavaScript because:

  • WebAssembly is faster than JavaScript in the file fetching phase. WebAssembly files are smaller than JavaScript even when JavaScript is compressed;
  • In the parsing phase, WebAssembly’s decoding time is shorter than JavaScript’s parsing time.
  • During compilation and optimization, WebAssembly has an advantage because the WebAssembly code is closer to machine code, whereas JavaScript is optimized on the server side first.
  • During the re-optimization phase, WebAssembly does not re-optimize. However, the optimization hypothesis of JS engine may occur the phenomenon of “discarding optimized code <-> re-optimization”.
  • At execution time, WebAssembly is faster because developers don’t have to know as much about the compiler, which is required in JavaScript. WebAssembly code is also better suited to generating instructions that the machine can execute more efficiently.
  • In the garbage collection phase, WebAssembly garbage collection is manually controlled and more efficient than automatic collection.

This is why, in most cases, WebAssembly performs the same task better than JavaScript.

However, there are also cases where WebAssembly may not perform as well as expected; At the same time, the future of WebAssembly will also be in the direction of making WebAssembly execution more efficient. I will cover this in the next WebAssembly series, The Present and Future of WebAssembly.


I’m currently working on a little book called React.js.