Optimizing Wasm and JS module size and load time.

Posted on Feb 23, 2023

Recently I’ve thought of a static search for this blog without the need to run a backend.

So I started to build the following ā€˜search engine’:

Diagram of the search engine’s project structure connecting Rust, Wasm and glue code

I don’t know until now if I will deploy this anytime, but it’s a good project to make use of a little bit functional programming, Rust and Wasm.

However, one problem I faced was the size of the Wasm module and JS overhead for the search.

You can see in the figure that there are at least 2 JS components and 1 Wasm module. The index is stored in a simple JSON file. The whole index of the current blog has a size of 2MB which leads to a loading time of ~50 seconds on ā€œSlow 3Gā€!

A long loading time is crashing the idea of static sites IMHO. Furthermore it is not so good to let visitors load the whole index even if they don’t use the search. Eating up their bandwidth is not my goal.

That’s why I started to explore how much load time and size I can save via optimization.

1. Optimizing .wasm size

One step towards a better load time, is the optimization of the size of the Wasm module. See also: Shrinking .wasm code size. This document presents some parameters which can be changed to shrink the size, as Wasm is usually optimized for speed rather than size!

Here are the three changed parameters and their outcome.

All load times are measured with a disabled cache and the ā€˜slow 3G’ param in Chrome DevTools.

Setting the following in Cargo.toml:

[profile.release]
lto = true

Had a huge effect on size change!

Parameter Before After Percentage change
Module size 149kB 115kB -22.81%
Load time 5.00s 4.31s -13.8%

1.2. Optimize for Size (instead speed):

In addition to the parameters in 1.1. the opt-level can be set to s:

[profile.release]
opt-level = 's'
Parameter Before After Percentage change
Module size 115kB 106kB -7.82%
Load time 4.31s 4.13s -4.17%

There’s also the z paramter. But the documentation says that it is not always shrinking the size in comparison to s and a measurement is needed. Which was true in this case:

[profile.release]
opt-level = 'z'
Parameter Before After Percentage change
Module size 106kB 127kB +19.81%
Load time 4.13s 4.55s +10.16%

1.3. Using the wasm-opt tool

The wasm-opt tool is also able to change the size:

Parameter Before After Percentage change
Module size 106kB 102kB -3.7%
Load time 4.13s 4.12s -0.24%

Unfortunately not so much.

Saved overall

Overall the decrease is pretty noticeable. Optimizing the module size is worth the small amount of work.

Module size: -31.54%
Load time: -17.6%

2. Optimizing loading of index

The even bigger part of the search is the index, which will also grow with the content. With 2MB and ~42 seconds of load time, it is eating the bandwidth.

Here are some actions and ideas to shrink the size and load time.

2.1. Split the index

The first step is obvious: Split the index and load only the parts that are needed.

In the case of my index it was easy: Every letter is stored in its own file. When the use starts typing, the indices get loaded lazily.

This has a huge effect: One of the biggest indices is the one for the letter c. This will be taken for the comparison.

Parameter Before After Percentage change
Module size (letter ā€˜c’) 2.0MB 153kB -92.35%
Load time 42.09s 5.10s -87.88%

The disadvantage is that the user will wait a small amount of time every time he starts to type in a new, not yet loaded, letter.

You can see the effect here:

2.2. Load only once

With the right techniques and caching it will help a lot to load the index only once. So that it is reusable even for a revisit of the page. But this will lead to some other challenges.

2.3. Serializing the index

Another technique to shrink the size of a document is to serialize it.

Using the avro schema led to an incredible decrease of -23.52% without losing any data!

Parameter Before After Percentage change
Module size (letter ā€˜c’) 153kB 117kB -23.52%

But the problem is, that the deserialization logic and package, either in Rust or JS was ~500KB in size. I didn’t do the math, but I think this doesn’t pay off for such small indices. I abandoned the idea.

3. Smaller js files

There are two JS files for the search: The interface to the Wasm module (search.js) and the glue code between the input field and the search interface (glue.js).

This can also be optimized:

search.js:

Parameter Before After Percentage change
Module size 16.1kB 9.3kB -42.23%
Load time 2.33s 2.22s -4.72%

glue.js:

Parameter Before After Percentage change
Module size 2.5kB 1.5kB -40%
Load time 2.05s 2.04s -0.48%

Notice the huge change of module size, but the small change of the load time.

4. Some other ideas

Beside of this low hanging fruits and the usage of parameters to change the size, there are some other possibilities to change the size and load time:

  • Smaller HTML files (will need an optimization for the whole blog).
  • Enabling brotli compression (which is not supported by my hosting services).
  • Using another data structure for the index like a Trie.

Conclusion

There are some easy ways to optimize the load times and size of Wasm and JS modules. Sometimes you just need to set some parameters to get a better user experience. Here I explored some of them and I could feel the change in the User Experience.

Want to know more?

Keep on reading and choose one of the related articles. You can also check the home page for my latest thoughts, notes and articles.