This article is translated

What makes WebAssembly Fast?

Originally written by Lin Clark

The original address: hacks.mozilla.org/2017/02/wha…

This is part five of the WebAssembly article series. If you haven’t read the others, I suggest youFrom the very beginning.

In my last article, I explained that WebAssembly and JavaScript aren’t necessarily two technologies. We don’t expect developers to write pure WebAssembly libraries.

This means that developers don’t have to choose between WebAssembly and JavaScript when developing applications. But we want developers to switch some of the JavaScript code in their projects to the WebAssembly version.

For example, the React development team can replace their Reconciler code, or Virtual DOM, with a WebAssembly version. People who use React don’t have to do anything… Their applications will work just as well as before and benefit from WebAssembly.

WebAssembly’s faster execution convinced the React team to make the switch. But what makes it faster?

How does JavaScript perform today?

Before we can understand the performance differences between JavaScript and WebAssembly, we need to understand what the JS engine is doing.

Here’s a rough sketch of how an application starts today.

The amount of time the JS engine spends on each task depends on the JavaScript code used on the page. The graph is not an accurate representation of performance data. Instead, the diagram provides a high-level model to compare the different performance of JS and WebAssembly for the same function.

The bars represent how long it takes to complete a particular task.

  • Parsing – The time it takes for the source code to be converted into something the interpreter can run.
  • Compilation + optimization – Time spent optimizing the compiler and baseline compiler. Some of the work of optimizing the compiler is not done on the main thread, so it is not included here.
  • Re-optimizing — Time to re-optimize if the JIT finds that its assumption has failed. Adjustments here include retuning and de-tuning (moving the optimized code back to the baseline code).
  • Execution – The time used to run code.
  • Garbage collection – Time spent cleaning up memory.

It is important to note that these tasks are not performed in a completely discrete manner, nor in any particular order. On the contrary, they are interlaced. Do some parsing, then do some compiling, then parse more, do more, and so on.

This breakdown of work has already resulted in a huge performance boost for JavaScript, and early JavaScript might look something like this:

The early days of running JavaScript with only the interpreter were very slow. With the introduction of JIT, the execution speed has been greatly improved.

The trade-off is that while monitoring and compilation costs are incurred, developers can reduce parsing and compilation time without changing the code. However, the performance gains will drive developers to create larger JavaScript applications.

That means there is still room for improvement.

How does WebAssembly compare?

I estimated how a traditional Web application would compare with WebAssembly.

Different browsers may handle these phases slightly differently. I’m going to use SpiderMonkey as an example.

pull

This stage is not shown in the diagram, but pulling files from the server can be time-consuming.

Because WebAssembly is more compressed than JavaScript, pulling is faster. Even if the package size of JavaScript is significantly reduced by compression algorithms, it is no smaller than the compressed binary representation of WebAssembly.

This means faster transfer between server and client. This is especially true on slow networks.

parsing

When JavaScript source code is pulled to the browser, it is converted into an AST (Abstract Syntax tree).

Browsers tend to execute lazily, parsing only the first things and creating “stubs” for functions that are not called.

After that, the AST is converted to IR (called bytecode) for JS engine specialization.

In contrast, WebAssembly does not need this transformation because it is already IR. It just needs to be decoded and checked for errors.

Compilation + optimization

As I explained in the JIT article, JavaScript is compiled during code execution. Depending on the type used at run time, you may need to compile different versions of the same code.

Different browsers compile WebAssembly differently. Some browsers do a baseline compilation of WebAssembly before starting to execute it, while others use the JIT.

In any case, WebAssembly was closer to machine code from the start. Take, for example, the types contained in the program. It’s faster because:

  1. The compiler does not have to spend time executing the code to observe the type being used before it starts compiling the optimized code.
  2. The compiler does not need to compile different versions of the same code based on the different types observed.
  3. Additional optimizations have been made in LLVM. So it takes the work out of compiling and optimizing the code.

To optimize the

Sometimes the JIT has to discard the optimized version of the code and re-optimize it.

This can happen if the JIT discovers that its assumptions are wrong based on executing code. For example, de-tuning occurs when variables entering the loop change during an iteration, or when a function is inserted into the prototype chain.

There are two costs to optimizing. First, it takes some time to drop the optimized code and fall back to the baseline version. Second, if the function is still called more than once, the JIT may choose to send the function code to the optimized compiler to compile again, which is another cost.

In WebAssembly, such things as types are explicit, so the JIT does not need to make assumptions based on runtime data. This means that WebAssembly does not need to go through a cycle of re-optimization.

perform

It is possible to write high-performance JavaScript. You need to understand the optimizations made by JIT to do this. For example, you need to know how to write code so that the compiler can be type-specific, as I explained in the JIT article.

However, most developers do not understand the ins and outs of JIT. Even for developers who know the ins and outs of the JIT, it can be difficult to get the program right. Many programming paradigms that help code make it more readable (for example, abstracting a common business into functions that can cross types) discourage the compiler from optimizing code.

Also, different browsers optimize using the JIT differently, so tuning for the internal details of one browser can degrade your code in another.

Therefore, it is usually faster to execute WebAssembly code. Many JIT optimizations for JavaScript (like type specificity) are not necessary for WebAssembly.

In addition, WebAssembly is designed to be a compiler target. In other words, WebAssembly is designed to be generated by compilers, not written by human developers.

Because human programmers don’t need to write WebAssembly directly, WebAssembly provides a more machine-friendly set of instructions. Depending on the type of code being executed, these instructions can help speed things up by anywhere from 10% to 800%.

The garbage collection

In JavaScript, developers don’t have to worry about cleaning unwanted old variables from memory. The JS engine handles this automatically using the garbage collector.

If you want predictable performance, it can be difficult. You have no control over how the garbage collector works, so it may start working at the wrong time. Most browsers do a good job of scheduling cleanup, but garbage collection is still an overhead for code execution.

At least for now, WebAssembly does not support garbage collection at all. Memory is managed manually (such as C and C++). While this makes development more difficult, it also makes the performance of the application more consistent.

conclusion

WebAssembly executes faster due to:

  • Pulling WebAssembly saves more time. Because WebAssembly is much more compressed than JavaScript.
  • WebAssembly decoding takes less time than parsing JavaScript.
  • Shorter compilation and optimization times. Because WebAssembly is closer to machine code than JavaScript, and WebAssembly has been optimized on the server side.
  • Reoptimization no longer occurs. Because WebAssembly already has information such as types built into it at compile time, the JS engine does not have to infer types during optimization as it does with JavaScript.
  • Shorter execution times. Because developers don’t need to know the subtleties of using compilers for more stable performance, WebAssembly’s instruction set is more machine-friendly.
  • Don’t rely on garbage collection. Because memory is managed manually.

This is where I think WebAssembly is superior to JavaScript for the same task.

In some cases, WebAssembly will not perform as expected. In addition, WebAssembly has some upcoming changes that will make it faster. I’ll touch on these points in my next post.