Ao trabalhar com o WebAssembly, é comum querer baixar um módulo, compilá-lo, instanciá-lo e, em seguida, usar o que for exportado em JavaScript. Nesta postagem, explicamos nossa abordagem recomendada para aumentar a eficiência.
Ao trabalhar com o WebAssembly, normalmente é recomendável fazer o download de um módulo, compilá-lo, instanciá-lo e usar o que for exportado em JavaScript. Esta postagem começa com um snippet de código comum, mas abaixo do ideal, que faz exatamente isso, discute várias otimizações possíveis e, por fim, mostra a forma mais simples e eficiente de executar o WebAssembly no JavaScript.
Esse snippet de código faz todo o processo download-compilar-instanciar, embora de uma forma abaixo do ideal:
Não use!
(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é que seja concluída. 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 abordar isso em breve.
Quase todas as operações no snippet modificado agora são assíncronas, já que 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 manter a consistência e manter a linha de execução principal
livre, podemos usar a WebAssembly.instantiate(module)
assíncrona.
(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 anteriormente. Com a compilação
de streaming, o navegador já pode
começar a compilar o módulo WebAssembly enquanto o download dos bytes do módulo ainda está em andamento. Como o download
e a compilação acontecem em paralelo, esse processo é mais rápido, especialmente para payloads grandes.
Para ativar essa otimização, use WebAssembly.compileStreaming
em vez de WebAssembly.compile
.
Essa mudança também nos permite eliminar o buffer da matriz intermediário, já que agora podemos transmitir diretamente a
instância Response
retornada 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 em uma instância
Response
. Se você não precisar de response
em outro lugar no seu código, transmita a promessa
retornada por fetch
diretamente, sem gerar await
de forma explícita:
(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 de 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 legível mantê-lo em uma linha separada.
Viu como compilamos a resposta em um módulo e a instanciamos imediatamente? Como resultado,
WebAssembly.instantiate
pode compilar e instanciar de uma só vez. A
API WebAssembly.instantiateStreaming
faz isso de maneira 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 única 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
- Usar APIs de streaming para compilar e instanciar módulos WebAssembly mais rapidamente
- Não escreva códigos desnecessários
Divirta-se com o WebAssembly!