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 de maneira não ideal:
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 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 permite que nos livremos 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 nenhum outro lugar, poderá até mesmo 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!