При работе с WebAssembly вам часто нужно загрузить модуль, скомпилировать его, создать его экземпляр, а затем использовать то, что он экспортирует в JavaScript. В этой статье объясняется наш рекомендуемый подход для оптимальной эффективности.
При работе с WebAssembly вы часто хотите загрузить модуль, скомпилировать его, создать его экземпляр, а затем использовать то, что он экспортирует в JavaScript. Этот пост начинается с распространенного, но неоптимального фрагмента кода, который делает именно это, обсуждает несколько возможных оптимизаций и в конечном итоге показывает самый простой и эффективный способ запуска WebAssembly из JavaScript.
Этот фрагмент кода выполняет полный цикл загрузки-компиляции-создания экземпляра, хотя и неоптимальным способом:
Не используйте это!
(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);
})();
Обратите внимание, как мы используем new WebAssembly.Module(buffer) для превращения буфера ответа в модуль. Это синхронный API, то есть он блокирует основной поток до тех пор, пока не завершится. Чтобы воспрепятствовать его использованию, Chrome отключает WebAssembly.Module для буферов размером более 4 КБ. Чтобы обойти ограничение по размеру, мы можем использовать 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) все еще не является оптимальным подходом, но мы вернемся к этому через секунду.
Почти каждая операция в измененном фрагменте теперь асинхронна, как ясно показывает использование await . Единственным исключением является new WebAssembly.Instance(module) , который имеет то же ограничение на размер буфера в 4 КБ в Chrome. Для согласованности и ради сохранения основного потока свободным мы можем использовать асинхронный 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);
})();
Давайте вернемся к оптимизации compile , на которую я намекнул ранее. При потоковой компиляции браузер может уже начать компилировать модуль WebAssembly, пока байты модуля еще загружаются. Поскольку загрузка и компиляция происходят параллельно, это быстрее — особенно для больших полезных нагрузок.

Чтобы включить эту оптимизацию, используйте WebAssembly.compileStreaming вместо WebAssembly.compile . Это изменение также позволяет нам избавиться от промежуточного буфера массива, поскольку теперь мы можем напрямую передавать экземпляр Response , возвращаемый 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);
})();
API WebAssembly.compileStreaming также принимает обещание, которое разрешается в экземпляр Response . Если у вас нет необходимости в response где-либо еще в вашем коде, вы можете передать обещание, возвращаемое fetch , напрямую, без явного await его результата:
(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);
})();
Если вам не нужен результат fetch где-либо еще, вы можете даже передать его напрямую:
(async () => {
const module = await WebAssembly.compileStreaming(
fetch('fibonacci.wasm'));
const instance = await WebAssembly.instantiate(module);
const result = instance.exports.fibonacci(42);
console.log(result);
})();
Хотя лично мне кажется более читабельным разместить это на отдельной строке.
Видите, как мы компилируем ответ в модуль, а затем немедленно создаем его экземпляр? Как оказалось, WebAssembly.instantiate может компилировать и создавать экземпляр за один раз. API WebAssembly.instantiateStreaming делает это в потоковом режиме:
(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);
})();
Если вам нужен только один экземпляр, нет смысла хранить объект module , что еще больше упрощает код:
// 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);
})();
Примененные нами оптимизации можно обобщить следующим образом:
- Используйте асинхронные API, чтобы избежать блокировки основного потока
- Используйте потоковые API для более быстрой компиляции и создания экземпляров модулей WebAssembly
- Не пишите код, который вам не нужен
Удачи в работе с WebAssembly!