Ao trabalhar com o WebAssembly, você geralmente quer fazer o download de um módulo, compilá-lo, instanciar e usar o que ele exporta em JavaScript. Esta postagem explica nossa abordagem recomendada para eficiência ideal.
Ao trabalhar com o WebAssembly, muitas vezes você quer fazer o download de um módulo, compilá-lo, instanciar e usar o que ele exporta em JavaScript. Esta postagem começa com um trecho de código comum, mas não ideal, fazendo exatamente isso, discute várias otimizações possíveis e, por fim, mostra a maneira mais simples e eficiente de executar o WebAssembly no JavaScript.
Este snippet de código faz a dança completa de download-compilação-instanciação, embora não seja a melhor maneira:
Não use isso.
(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);
})();
Observe como usamos new WebAssembly.Module(buffer) para transformar um buffer de resposta em um módulo. Essa é uma
API síncrona, o que significa que ela bloqueia a linha de execução principal até a conclusão. Para desencorajar o uso, o Chrome
desativa o WebAssembly.Module para buffers maiores que 4 KB. Para contornar o limite de tamanho, podemos
usar await WebAssembly.compile(buffer):
(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);
})();
await WebAssembly.compile(buffer) ainda não é a abordagem ideal, mas vamos chegar lá em
um segundo.
Quase todas as operações no snippet modificado agora são assíncronas, como o uso de await deixa
claro. A única exceção é new WebAssembly.Instance(module), que tem a mesma restrição de tamanho de buffer de 4 KB
no Chrome. Para consistência e para manter a linha de execução principal
livre, podemos usar o WebAssembly.instantiate(module)
assíncrono.
(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);
})();
Vamos voltar à otimização de compile que mencionei anteriormente. Com a compilação
em streaming, o navegador já pode
começar a compilar o módulo WebAssembly enquanto os bytes do módulo ainda estão sendo transferidos. Como o download
e a compilação acontecem em paralelo, isso é mais rápido, principalmente para payloads grandes.
Para ativar essa otimização, use WebAssembly.compileStreaming em vez de WebAssembly.compile.
Essa mudança também nos permite nos livrar do buffer de matriz intermediário, já que agora podemos transmitir a
instância Response retornada por await fetch(url) diretamente.
(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);
})();
A API WebAssembly.compileStreaming também aceita uma promessa que é resolvida para uma instância
Response. Se você não precisar de response em outro lugar do código, transmita diretamente a promessa
retornada por fetch, sem await o resultado explicitamente:
(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);
})();
Se você não precisar do resultado fetch em outro lugar, poderá até transmiti-lo diretamente:
(async () => {
const module = await WebAssembly.compileStreaming(
fetch('fibonacci.wasm'));
const instance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
Pessoalmente, acho mais fácil ler quando está em uma linha separada.
Percebeu como compilamos a resposta em um módulo e a instanciamos imediatamente? O
WebAssembly.instantiate pode compilar e instanciar de uma só vez. A
API WebAssembly.instantiateStreaming faz isso de maneira contínua:
(async () => {
const fetchPromise = fetch('fibonacci.wasm');
const { module, instance } = await WebAssembly.instantiateStreaming(fetchPromise);
// To create a new instance later:
const otherInstance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
Se você precisar apenas de uma instância, não há necessidade de manter o objeto module,
simplificando ainda mais o código:
// This is our recommended way of loading WebAssembly.
(async () => {
const fetchPromise = fetch('fibonacci.wasm');
const { instance } = await WebAssembly.instantiateStreaming(fetchPromise);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
As otimizações aplicadas podem ser resumidas da seguinte maneira:
- Usar APIs assíncronas para evitar o bloqueio da linha de execução principal
- Usar APIs de streaming para compilar e instanciar módulos do WebAssembly com mais rapidez
- Não escreva código que você não precisa
Divirta-se com o WebAssembly!