Ao trabalhar com o WebAssembly, é comum fazer o download de um módulo, compilá-lo, instanciá-lo e usar o que ele exportar em JavaScript. Esta postagem explica nossa abordagem recomendada para alcançar a eficiência máxima.
Ao trabalhar com o WebAssembly, é comum fazer o download de um módulo, compilá-lo, instanciá-lo e e usar o que ele exportar em JavaScript. Esta publicação começa com um código comum, mas abaixo do ideal snippet que faz exatamente isso, discute várias otimizações possíveis e, por fim, mostra os maneira mais simples e eficiente de executar o WebAssembly em JavaScript.
Este snippet de código faz a dança completa de download-compile-instantiate, embora de uma forma abaixo do 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. Esta é uma
síncrona, o que significa que ela bloqueia a linha de execução principal até que seja concluída. Para desencorajar seu uso, o Chrome
desativa WebAssembly.Module
para buffers maiores que 4 KB. Para contornar o limite de tamanho, podemos
use 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 abordar isso em um
segundo.
Quase todas as operações no snippet modificado agora são assíncronas, já que o uso de await
facilita
claras. A única exceção é new WebAssembly.Instance(module)
, que tem o mesmo buffer de 4 KB
restrição de tamanho no Chrome. Para consistência e a fim de manter a linha de execução principal
sem custo financeiro, podemos usar o modelo
WebAssembly.instantiate(module)
(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 do compile
que sugerimos antes. Com streaming
compilação, o navegador já pode
começará a compilar o módulo WebAssembly durante o download dos bytes do módulo. Desde o download
e a compilação acontecem em paralelo, isso é mais rápido — especialmente para payloads grandes.
Para ativar essa otimização, use WebAssembly.compileStreaming
em vez de WebAssembly.compile
.
Essa alteração também nos permite eliminar o buffer intermediário da matriz, já que agora podemos passar o
Instância de Response
retornada diretamente por 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);
})();
A API WebAssembly.compileStreaming
também aceita uma promessa que é resolvida como Response
.
instância. Se você não precisar de response
em outro lugar no código, poderá transmitir a promessa.
retornados diretamente por fetch
, sem await
explicitamente o resultado:
(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ê também não precisar do resultado fetch
em outro lugar, poderá 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);
})();
No entanto, pessoalmente, acho mais legível mantê-lo em uma linha separada.
Viu como compilamos a resposta em um módulo e a instanciamos imediatamente? Como se consta,
WebAssembly.instantiate
pode compilar e instanciar de uma só vez. A
A API WebAssembly.instantiateStreaming
faz isso de modo streaming:
(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ê só precisa de uma instância, não faz sentido 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 que aplicamos podem ser resumidas da seguinte forma:
- Usar APIs assíncronas para evitar o bloqueio da linha de execução principal
- Use APIs de streaming para compilar e instanciar módulos WebAssembly mais rapidamente
- Não escreva códigos desnecessários
Divirta-se com o WebAssembly!