Increase Rust and WebAssembly performance 🚀🚀🚀
Tags: javascript, rust, webassembly, webdev
Published: 2019-07-02 00:40:25

The dream of running native code in the browser is not something new. There were many failed attempts. They all taught us a lesson. Those learnings made `WebAssembly` possible today. 

WebAssembly makes it possible to run languages like C, C++, Rust and other languages in the browser. 

---

Check out my book on Rust and WebAssembly [here](https://sendilkumarn.com/wasm-book)

---

But what is WebAssembly? Check out this presentation [here](https://speakerdeck.com/sendilkumarn/webassembly-for-the-masses) or [this](https://hacks.mozilla.org/2017/02/a-cartoon-intro-to-webassembly/) awesome post from Lin Clark.

## TL;DR:

 * Rust's toolchain makes it easy write WebAssembly application.
 * If you want better performance then use `opt-level=3`.
 * If you want a smaller sized bundle then use `opt-level="s"`. 

# What are we gonna do?

Create a WebAssembly application that takes a string in `markdown` format and converts that into HTML.

# Lets get started

So far, Rust has the best tooling for the WebAssembly. It is well integrated with the language. This makes Rust the best choice for doing WebAssembly.

We will need to install Rust before getting started. To install Rust checkout the installation guide [here](https://www.rust-lang.org/tools/install).

Once you have the Rust installed. Let's start creating the application.

# Create Application 

Create a WebAssembly application with all the necessary toolchain:

```bash
npm init rust-webpack markdown-rust
```

This creates a new Rust + JavaScript based application with Webpack.

Go inside the directory

```bash
cd markdown-rust
```

It has both `Cargo.toml` and `package.json`.

The Rust source files are present in the `src` directory and the JavaScript files are available in `js` directory. We also have webpack configured for running the application easy and fast.

The `Cargo.toml` contains the following:

```toml
[package]
# Some package information.
```

Then it declares the project will build a `dynamic library` with the following command.

```toml
[lib]
crate-type = ["cdylib"]
```

We have also declared the release profile should optimize the release using `lto` flag.

```toml
[profile.release]
lto = true
```

Finally added some `[features]` and `[depdencies]`.

Now all We have to do is add the `markdown` library for the Rust that compiles the Markdown (string) into HTML string.

```toml
[dependencies]
# some comments ......
wasm-bindgen = "0.2.45"
comrak = "0.6"
```

Remove all the contents from `src/lib.rs` and replace that with the following.

Load the `comrak` functions and `wasm_bindgen` that we will be using.

```rust
use comrak::{markdown_to_html, ComrakOptions};
use wasm_bindgen::prelude::*;
```

#### So what is `wasm_bindgen`? 

WebAssembly does not have any bindings to call the JavaScript or Document APIs. In fact, we can only pass numbers between JavaScript and WebAssembly. But that is not always desirable right, we need to pass JS objects, Strings, classes, closures and others between them. 

> How can we achieve that? 

We can create a binding file or glue file that helps to translate the above objects into numbers. For example, in case of the string rather than sending each character as a character code. 

We can put that string in a linear memory array and then pass the start-index (of where it is in memory) and its length to the other world (or JavaScript). The other world should have access to this linear memory array and fetches the information from there. 

But doing this for every value that we pass between JavaScript and WebAssembly is time-consuming and error-prone. The [wasm_bindgen](https://rustwasm.github.io/wasm-bindgen/) tool helps you to build the binding file automatically and also removes the boilerplate code with a single `#[wasm_bindgen]` annotation. 

But we need to be very careful about how many times we cross the boundary between JavaScript and WebAssembly module. More we cross slower the performance will be.

Now we will create a function called parse that actually takes the markdown input and returns the HTML.

```rust
#[wasm_bindgen]
pub fn parse(input: &str) -> String {
    markdown_to_html(&input.to_string(), &ComrakOptions::default())
}
```

The `#[wasm_bindgen]` annotation does all the boilerplate of converting the string into two numbers, one for the pointer to the start of the string in the linear memory and the other for the length of the string. The `#[wasm_bindgen]` also generates the binding file in JavaScript.

#### Time for some JavaScript :heart:

Now we have the WebAssembly Module ready. It is time for some JavaScript.

We will remove all the lines from the `js/index.js` and replace that with the following contents.

We will first import the WebAssembly module generated. Since we are using Webpack, Webpack will take care of bootstrapping `wasm_pack` that will, in turn, use the `wasm_bindgen` to convert Rust into WebAssembly module and then generate the necessary binding files. 

The `wasm_pack` is a tool that helps to build and pack the Rust and WebAssembly applications. More about Wasm-pack [here](https://rustwasm.github.io/wasm-pack/).

This means we have to just import the `pkg/index.js` file. This is where wasm_pack will generate the output.

```javascript
const rust = import('../pkg/index.js');
```

The dynamic import will create promise which when resolved gives the result of the WebAssembly modules. We can call the function `parse` defined inside the Rust file like below.

```javascript
rust.then(module => {
    console.log(module.parse('#some markdown content')); 
});
```

We will also calculate the time it took to parse the contents using the WebAssembly module. 

```javascript
rust.then(module => {
    console.log(module.parse('#some markdown content'));

    const startWasm = performance.now();
    module.parse('#Heading 1');
    const endWasm = performance.now();

    console.log(`It took ${endWasm - startWasm} to do this in WebAssembly`);
});
```
For comparison, we will also calculate the time it took to do it with JavaScript.

Install the markdown library for the JavaScript.

```bash
npm install --save marked
```

Once installed, let us write our JavaScript code that takes in a Markdown text and returns the HTML.


```javascript

// js/index.js
import marked from 'marked';
// some content goes here;

const markdown = '#Heading';

const startJs = performance.now();
console.log(marked(markdown));
const endJs = performance.now();

console.log(`It took ${endJs - startJs} to do this in JavaScript`);

```

Let us run the application using `npm run start`. This will kick start the Webpack dev server and serve the content from the local.

{% codesandbox xenodochial-tesla-rimzu %}


It is quite an interesting performance statistics to look at. 

In Chrome and Safari, the JavaScript performance is way better than the WebAssembly. But in Firefox the JavaScript version is __50% slower__ than the WebAssembly. 

This is mainly because WebAssembly linking and bootstrapping is very very fast in Firefox than compared with any other browser.

If you take a look at the bundle size, the WebAssembly file is mammoth 7475 KB than compared with the JavaScript variant 1009 KB.

If you are booing for WebAssembly now, then wait.

We did not add any optimizations yet. Let us add some optimizations and check the performance. 

Open the `Cargo.toml` file and add the following segment above the `[features]` section.

```toml
[profile.dev]
lto = true
opt-level = 3
```

The `opt-level` is nothing but optimization level for compiling the project. 

The `lto` here refers to `link-time-optimization`. 

> Note: This optimization level and lto should be added to the `profile.release` while working on the real application.

Additionally, enable the `wee_alloc` which does a much smaller memory allocation. 

Uncomment the following in the `Cargo.toml`

```toml
[features]
default = ["wee_alloc"]
```

Add the `wee_alloc` memory allocation inside the `src/lib.rs` file.

```rust
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

```

Now let us restart the server.


{% codesandbox hidden-sun-cdhms %}

We can now see the real performance benefits of the WebAssembly. 
In Chrome the WebAssembly version is 4 times faster than the JavaScript version. 

In Safari, the JavaScript variant is still between 2-3 ms but the WebAssembly variant is between 0-2ms.

Firefox too saw almost 50% faster WebAssembly code when using the optimizations than without optimizations.

Now the all-important bundle size is 1280 KB for WebAssembly and 1009 KB for JavaScript.

We can also ask Rust compiler to optimize for size rather than speed. To specify that change the  `opt-level` to `s`

```toml
opt-level = "s"
```

{% codesandbox beautiful-clarke-cr649 %}


WebAssembly still is a clear winner, but the Chrome registers slightly increased WebAssembly times but still lesser than the JavaScript variant. Both Safari and Firefox provide higher performance for the WebAssembly.

The bundle size is reduced further for WebAssembly at around 1220 and 1009 KB for JavaScript.

Rust compiler also supports `opt-level = "z"` which reduces the file size even further. 

```toml
opt-level = "z"
```
{% codesandbox upbeat-ritchie-43rth %}

The bundle size is reduced further for WebAssembly at around 1161KB and 1009 KB for JavaScript.

The performance of the WebAssembly module in Chrome is fluctuating a lot when using `opt-level='z'` between 41 and 140 ms.

_IE Canary for Mac has (~)almost the same performance as of Chrome._

Use `opt-level="z"` if you are more concerned about your bundle size but the performance is not reliable in v8 now.

I hope this gives you a motivation to kick start your awesome WebAssembly journey. If you have any questions/suggestions/feel that I missed something feel free to add a comment.

---

If you have enjoyed the post, then you might like my book on Rust and WebAssembly. Check them out [here](https://sendilkumarn.com/wasm-book)

---

You can follow me on [Twitter](https://twitter.com/sendilkumarn).

If you like this article, please leave a like or a comment. :heart: