При работе с 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!