Original title:Loading WebAssembly modules efficiently

Original link:https://developers.google.com/web/updates/2018/04/loading-wasm(Need to cross the wall)

By Mathias Bynens


When we use WebAssembly, we usually download a module, compile it, instantiate it, and then export it using JavaScript. This article will start with a common but not optimal code for doing this, then discuss a few areas where you can optimize, and end with the simplest and most efficient way to do it.

Tip: tools like Emsciptent can automatically generate template code for you to do this, so you don’t have to write it yourself. The purpose of this article is to provide the following best practices to help you in case you need to fine-tune the loading of WebAssembly modules.

The following code is a complete implementation of the above “download-compile-instantiation”, but in a less optimized way:

// Don't take this approach
(async() = > {const response = await fetch('fibonacci.wasm');
  const buffer = await response.arrayBuffer();
  const module = new WebAssembly.Module(buffer);
  const instance = new WebAssembly.Instance(module);
  const result = instance.exports.fibonacci(42);
  console.log(result); }) ();Copy the code

Note that we use new Webassembly.module (buffer) to convert a response buffer into a Module buffer. However, the API is synchronous, which means it blocks the main thread until it finishes executing. To discourage its use, Chrome disallows its use when the buffer size exceeds 4KB. To get around this limitation, we can use await WebAssembly.compile(buffer) instead:

(async() = > {const response = await fetch('fibonacci.wasm');
  const buffer = await response.arrayBuffer();
  const module = await WebAssembly.compile(buffer);
  const instance = new WebAssembly.Instance(module);
  const result = instance.exports.fibonacci(42);
  console.log(result); }) ();Copy the code

Await WebAssembly.compile(buffer) is not the optimal method, the optimal method will be known later.

From the use of await in the tweaked code above, we can see that almost all operations are asynchronous. The only exception is new Webassembly.instance (module), which is also limited by Chrome’s “4KB buffer size.” Instead of using asynchronous WebAssembly.Instantiate (Module) for consistency and “keeping the main thread free from interruptions at all times”.

(async() = > {const response = await fetch('fibonacci.wasm');
  const buffer = await response.arrayBuffer();
  const module = await WebAssembly.compile(buffer);
  const instance = await WebAssembly.instantiate(module);
  const result = instance.exports.fibonacci(42);
  console.log(result); }) ();Copy the code

Now let’s look at the optimal way to compile as I mentioned earlier. Thanks to “Streaming Compilation,” browsers can now compile WebAssembly modules directly while the module data is still being downloaded. Since downloads and compilations happen simultaneously, they are naturally faster — especially when the payload is large.



To use this optimization, we need to change the use of the WebAssebly.com running for WebAssembly.com pileStreaming. This change also helps us avoid intermediate arrayBuffers, since we are now passing an instance of Response returned directly from an await fetch(URL) :

(async() = > {const response = await fetch('fibonacci.wasm');
  const module = await WebAssembly.compileStreaming(response);
  const instance = await WebAssembly.instantiate(module);
  const result = instance.exports.fibonacci(42);
  console.log(result); }) ();Copy the code

Note: This method requires that the server do the correct MIME type configuration for the.wasm file by sendingContent-Type: application/wasmHead. In the previous example, this step was not necessary because we were passing an ArrayBuffer of Response, so no detection of the MIME type would occur.

WebAssembly.com pileStreaming API also supports the incoming can parse (resolve) for the Response of the promise. If you don’t have anywhere else in your code to use response, then you can pass the promise returned by the fetch directly, without awaiting the result:

(async() = > {const fetchPromise = fetch('fibonacci.wasm');
  const module = await WebAssembly.compileStreaming(fetchPromise);
  const instance = await WebAssembly.instantiate(module);
  const result = instance.exports.fibonacci(42);
  console.log(result); }) ();Copy the code

If there is no other need to use the fetch return, you can simply pass it:

(async() = > {const module = await WebAssembly.compileStreaming(
    fetch('fibonacci.wasm'));
  const instance = await WebAssembly.instantiate(module);
  const result = instance.exports.fibonacci(42);
  console.log(result); }) ();Copy the code

That said, I personally find it more readable to put it on its own line.

See how we compiled Response into a Module and instantiated it immediately? In fact, WebAssembly.instantiate can be compiled and instantiated in one step. WebAssembly instantiateStreaming API can also, of course, and is the flow:

(async () => {
  const fetchPromise = fetch('fibonacci.wasm'); const { module, instance } = await WebAssembly.instantiateStreaming(fetchPromise); // Create another new instance later: const otherInstance = await webassembly.instantiate (module); const result = instance.exports.fibonacci(42); console.log(result); }) ();Copy the code

There is no point in saving the Module object if you only need one instance, so the code can be further simplified:

// This is how we recommend loading WebAssembley
(async() = > {const fetchPromise = fetch('fibonacci.wasm');
  const { instance } = await WebAssembly.instantiateStreaming(fetchPromise);
  const result = instance.exports.fibonacci(42);
  console.log(result); }) ();Copy the code

You can play with this code sample online in WebAssembly Studio.

To summarize the optimizations we applied:

  • Use asynchronous apis to prevent main thread blocking
  • Use streaming apis to compile and instantiate WebAssembly modules faster
  • Don’t write, don’t code

Have fun with WebAssembly!