4 min read

Yesterday, Das Surma, a Web Advocate at Google, shared how he and his team replaced a JavaScript hot path in the Squoosh app with WebAssembly. Squoosh is an image compression web app which allows you to compress images with a variety of codecs that have been compiled from C++ to WebAssembly. Hot paths are basically code execution paths where most of the execution time is spent.

With this update, they aimed to achieve predictable performance across all browsers. Its strict typing and low-level architecture enable more optimizations during compilation. Though JavaScript can also achieve similar performance to WebAssembly, it is often difficult to stay on the fast path.

What is WebAssembly?

WebAssembly, also known as Wasm, provides you with a way to execute code written in different languages at near-native speed on the way. It is a low-level language with a compact binary format, which provides C/C++/Rust as the compilation target so that they can run on the web.

When you compile a C or Rust code to WebAssembly, you get a .wasm file. This file contains something called “module declaration”. In addition to the binary instructions for the functions contained within, it contains all the imports the module needs from its environment and a list of exports this module provides to the host.

Comparing the file size generated

To narrow down the language, Surma gave an example of a JavaScript function that rotates an image by multiples of 90 degrees. This function basically iterates over every pixel of an image and copies it to a different location. This function was written in three different languages, C/C++, Rust, AssemblyScript, and was compiled to WebAssembly.

C and Emscripten

Emscripten is a C compiler that allows you to easily compile your C code to WebAssembly. After porting the entire JavaScript code to C and compiling it with emcc, Emscripten creates a glue code file called c.js and wasm module called c.wasm. The wasm module gzipped to almost 260 bytes and the c.js file was of the size 3.5 KB.

Rust

Rust is a programming language syntactically similar to C++. It is designed to provide better memory and thread-safety. The Rust team has introduced various tooling to the WebAssembly ecosystem, and one of them is wasm-pack. With the help of wasm-pack, developers can turn their code into modules that work out-of-the-box with bundlers like Webpack. After compiling the Rust code using wasm-pack, a 7.6 KB wasm module was generated with about 100 bytes of glue code.

AssemblyScript

AssemblyScript compiles a strictly-typed subset of TypeScript to WebAssembly ahead of time. It uses the same syntax as TypeScript but switches the standard library with its own. This essentially means that you can’t just compile any TypeScript to WebAssembly, but you don’t have to learn a new programming language to write WebAssembly. After installing the AssemblyScript file, with the help of the AssemblyScript/assemblyscript npm package, AssemblyScript provides with a wasm module of at least 300 bytes and no glue code. The module can directly work with vanilla WebAssembly APIs.

Comparing the size of files generated by compiling the above three languages, Rust gave the biggest file.

Comparing the performance

To analyze the performance, the team did speed comparison per language and speed comparison per browser. They shared the results in the following two graphs:

Source: Google Developers

The graphs show that all the WebAssembly modules were executed in ~500ms or less, which proves that WebAssembly gives a predictable performance. Regardless of which language you choose, the variance between browsers and languages is minimal. The standard deviation of JavaScript across all browsers is ~400ms. And, the standard deviation of all our WebAssembly modules across all browsers is ~80ms.

Which language you should choose if you have a JS hot path and want to make it faster with WebAssembly?

Looking at the above results, the best choice seems to be C or AssemblyScript, but they decided to go with Rust. They narrowed down to Rust because all the codecs shipped in Squoosh so far are compiled using Emscripten and the team wanted to broaden their knowledge about the WebAssembly ecosystem by using a different language. They did not choose AssemblyScript because it is relatively new and the compiler is not as mature as Rust.

The file size difference between Rust and other languages were quite huge but in reality, this is not a big deal. Going by the runtime performance, Rust showed a faster average across browsers than AssemblyScript. Additionally, Rust will be more likely to produce faster code without requiring any manual code optimizations.

To read more in detail, check out Surma’s post on Google Developers.

Read Next

Introducing CT-Wasm, a type-driven extension to WebAssembly for secure, in-browser cryptography

Creating and loading a WebAssembly module with Emscripten’s glue code [Tutorial]

The elements of WebAssembly – Wat and Wasm, explained [Tutorial]