Статьи

WebAssembly: решение проблем с производительностью в Интернете

В современном JavaScript цель часто состоит в том, чтобы найти все способы оптимизировать производительность в браузере. Бывают случаи, когда веб-приложения требуют высокой производительности и ожидают, что браузеры будут работать.

Традиционный JavaScript имеет ограничения производительности из-за того, как движок обрабатывает язык. Интерпретированный (или даже JIT-скомпилированный) язык, который отображается как часть страницы, может получить только очень многое — даже от самого мощного оборудования.

WebAssembly разработан с нуля, чтобы решить проблему производительности. Это может преодолеть узкие места, которые не должен был решать традиционный JavaScript. В WebAssembly нет необходимости анализировать и интерпретировать код. WebAssembly в полной мере использует свой формат байт-кода, чтобы предоставить вам скорости выполнения, соответствующие собственным программам.

Подумайте об этом иначе: представьте традиционный JavaScript как хороший универсальный инструмент, который поможет вам в любом месте. WebAssembly, напротив, является высокопроизводительным решением, способным достигать почти родных скоростей. Это два отдельных инструмента программирования в вашем распоряжении.

У меня следующие вопросы: заменяет ли WebAssembly старый добрый традиционный JavaScript? Если нет, то стоит ли инвестировать в изучение WebAssembly?

Что такое WebAssembly?

WebAssembly — это другой тип кода, который можно отправить в браузер. Это в байт-коде , формат означает, что он поставляется на языке ассемблера низкого уровня к тому времени, когда он достигает браузера. Байт-код не предназначен для написания от руки, но может быть скомпилирован из любого языка программирования, такого как C ++ или Rust. Затем браузер может взять любой код WebAssembly, загрузить его как собственный код и добиться высокой производительности.

Вы можете думать об этом байт-коде WebAssembly как о модуле: браузер может извлечь модуль, загрузить и выполнить его. Каждый модуль WebAssembly имеет возможности импорта и экспорта, которые во многом похожи на объекты JavaScript. Модуль WebAssembly действует во многом как любой другой код JavaScript, за исключением того факта, что он работает с почти родной скоростью. С точки зрения программиста, вы можете работать с модулями WebAssembly так же, как вы работаете с текущими объектами JavaScript. Это означает, что то, что вы уже знаете о JavaScript и Интернете, переносится и в программирование WebAssembly.

Инструмент WebAssembly часто состоит из компилятора C ++. В настоящее время существует множество инструментов для разработки, но Emscripten достиг зрелости. Этот инструмент компилирует код C ++ в модуль WebAssembly и создает совместимые со стандартами модули, которые могут работать где угодно. Скомпилированный вывод будет иметь расширение файла WASM, чтобы указать, что это модуль WebAssembly.

Одним из преимуществ WebAssembly является то, что у вас все те же заголовки кэширования HTTP, когда вы выбираете модули. Кроме того, вы можете кэшировать модули WASM, используя IndexedDB, или вы можете кэшировать модули, используя хранилище сеансов . Стратегия кэширования вращается вокруг кэширования запросов API выборки и позволяет избежать еще одного запроса, сохраняя локальную копию. Поскольку модули WebAssembly имеют формат байт-кода, вы можете рассматривать модуль как байтовый массив и хранить его локально.

Теперь, когда мы знаем, что такое WebAssembly, каковы некоторые из его ограничений?

Известные ограничения

JavaScript работает в среде, отличной от любой типичной программы на C ++. Следовательно, ограничения включают то, что нативные API могут делать в среде браузера.

Сетевые функции должны быть асинхронными и неблокирующими операциями. Все базовые сетевые функции JavaScript являются асинхронными в веб-API браузера. WebAssembly, однако, не получает выгоды от асинхронных операций ввода-вывода. Операция ввода-вывода должна ожидать ответа сети, что делает практически все прирост производительности почти невидимым.

Код, который выполняется в браузере, работает в изолированной среде и не имеет доступа к файловой системе. Вместо этого вы можете создать виртуальную файловую систему в памяти, которая поставляется с предварительно загруженными данными.

Основной цикл приложения использует совместную многозадачность, и каждое событие имеет ход для выполнения. Событие в сети часто происходит от щелчка мышью, касания пальцем или операции перетаскивания. Событие должно вернуть управление браузеру, чтобы другие события могли быть обработаны. Целесообразно избегать перехвата основного цикла событий, поскольку это может превратиться в кошмар отладки. События DOM часто связаны с обновлениями пользовательского интерфейса, которые стоят дорого. И это подводит нас к другому ограничению.

WebAssembly не может получить доступ к DOM; он использует функции JavaScript для внесения любых изменений. В настоящее время есть предложение разрешить взаимодействие с объектами DOM в сети . Если вы думаете об этом, перерисовки DOM являются медленными и дорогими. Все выгоды, которые можно получить от почти естественной производительности, сводятся на нет DOM. Одним из решений является абстрагирование DOM в локальную копию в памяти, которая позже может быть согласована с помощью JavaScript.

В WebAssembly хороший совет — придерживаться того, что может работать очень быстро. Используйте инструмент для работы, который дает наибольшее повышение производительности, избегая при этом ловушек. Думайте о WebAssembly как об этой сверхскоростной системе, которая хорошо работает в изоляции без каких-либо блокирующих устройств.

Совместимость браузеров в WebAssembly является мрачной, за исключением современных браузеров. Там нет поддержки в IE. Однако Edge 16+ поддерживает WebAssembly. Все современные крупные игроки, такие как Firefox 52+, Safari 11+ и Chrome 57+, поддерживают WebAssembly. Одна из идей состоит в том, чтобы обнаруживать функции и делать паритет функций между модулями WebAssembly и откат к JavaScript. Таким образом, вы не нарушаете работу сети, а современные браузеры получают все преимущества от производительности WebAssembly.

WebAssembly Демо

Хватит говорить; время для хорошей демонстрации. На этот раз мы рассмотрим функции экспорта и импорта в WebAssembly. Функции экспорта и импорта являются отличительными признаками совместимости с WebAssembly. Эти функции позволяют программистам работать с модулями WebAssembly, как и любой другой объект JavaScript.

Функция экспорта — это функция, которую вы получаете из модуля WebAssembly. Как только модуль загрузится, вы найдете функцию экспорта внутри instance.exports . Для этой демонстрации я экспортирую функцию add которая вычисляет сумму двух чисел, которые вы передаете в качестве параметров. Расчет будет выполнен в почти нативном коде WebAssembly. В этой демонстрации функция экспорта будет чисто JavaScript-функцией — это означает, что она не имеет состояния и неизменна.

Функция импорта — это функция, которую вы вводите в модуль WebAssembly. Это простой старый объект JavaScript с функцией обратного вызова. Затем модуль вызывает функцию с параметрами из WebAssembly. Я импортирую простой обратный вызов, который получает один параметр из WebAssembly. Параметру является константа, которой присвоено значение 42. Затем я буду использовать это значение для установки DOM из JavaScript:

 <p>Add result: <span id="addResult"></span></p> <p>Simple result: <span id="simpleResult"></span></p> 

Экспортированная функция WebAssembly

Сначала посмотрим на текстовый формат модуля WebAssembly. Это текстовое представление модуля WASM, которое может быть прочитано людьми. Он предназначен для текстовых редакторов или любого другого инструмента, который может работать с простым текстом:

 (module (func $add (param $lhs i32) (param $rhs i32) (result i32) get_local $lhs get_local $rhs i32.add) (export "add" (func $add))) 

Не слишком важно понимать каждую деталь здесь. Это текстовый формат модуля WebAssembly, который вы часто найдете с расширением файла WAT. i32.add выполняет сложение, используя почти нативный код. Затем export "add" захватывает func $add и делает его доступным для JavaScript.

Чтобы загрузить модуль WebAssembly, вы можете сделать это:

 // URL to the WASM module const WASM_ADD_MODULE = 'https://myhost.com/add.wasm'; fetch(WASM_ADD_MODULE) .then(response => response.arrayBuffer()) .then(bytes => WebAssembly.instantiate(bytes)) .then(result => document.getElementById('addResult').innerHTML = result.instance.exports.add(1, 5)); 

Fetch API получает модуль из URL и превращает его в байтовый массив. Этот байтовый массив взят из response.arrayBuffer . Обратите внимание, что проверка экспортированной функции exports.add говорит, что она скомпилирована как собственный код:

 function 0() { [native code] } 

Одна проблема заключается в том, что использование WebAssembly.instantiate более снисходительно, чем WebAssembly.instantiateStreaming . Последний говорит, что модули WASM должны иметь MIME-тип application/wasm . Вы столкнетесь с этой проблемой, когда получите TypeError во время работы с ним. Если вы обслуживаете модули WASM через CDN и не можете контролировать тип MIME, используйте WebAssembly.instantiate . WebAssembly.instantiateStreaming более эффективен, чем предыдущий, но это более новый веб-API, поэтому он пока недоступен во всех современных браузерах.

Импортированная функция WebAssembly

Для импортированных функций начните с этого модуля в текстовом формате. Представьте себе выполнение этого дорогостоящего вычисления с привязкой к процессору в WebAssembly. На самом деле, настолько интенсивно, что это ответ на главный вопрос жизни и всего остального.

Например:

 (module (func $i (import "imports" "imported_func") (param i32)) (func (export "exported_func") i32.const 42 call $i)) 

Обратите внимание на объявленную константу i32.const 42 . Затем возьмите импортированную функцию и вызовите функцию обратного вызова с call $i . export "exported_func" объявляет имя экспортируемой функции, вызываемой из JavaScript.

В JavaScript мы можем работать с этим модулем следующим образом:

 const WASM_SIMPLE_MODULE = 'https://myhost.com/simple.wasm'; const simpleFn = (arg) => document.getElementById('simpleResult').innerHTML = arg; const importSimpleObj = {imports: {imported_func: simpleFn}}; fetch(WASM_SIMPLE_MODULE) .then(response => response.arrayBuffer()) .then(bytes => WebAssembly.instantiate(bytes, importSimpleObj)) .then(result => result.instance.exports.exported_func()); 

Посмотрите на importSimpleObj , так как это объект JavaScript, который имеет функцию обратного вызова. Затем exports.exported_func выполняет модуль WebAssembly. После simpleFn импортированная функция simpleFn запускается с постоянным параметром.

Ниже приведена демонстрация CodePen, с которой вы можете поиграть. Не стесняйтесь проверять каждую функцию и объект в этом примере кода. Это даст хорошее представление о связующем коде, необходимом для интеграции с WebAssembly.

Вывод

Чтобы ответить на мои первые оригинальные вопросы, WebAssembly — хорошее дополнение к Сети. Он не предназначен для замены JavaScript, а только расширяет возможности современных веб-технологий. Любой веб-инженер, ищущий скорость, эффективность и высокую производительность, должен обратить внимание на WebAssembly. JavaScript работает как связующий код, который выполняет и обрабатывает результат из WebAssembly.

Одна из идей заключается в портировании существующего кода JavaScript, который выполняет большую часть работы с процессором — скажем, виртуальное представление DOM в памяти, которое только абстрагирует реальный DOM. Порт WebAssembly, например, также может предоставить элегантный запасной вариант для браузеров, которые еще не поддерживают WebAssembly.

Поскольку модули WebAssembly становятся все более распространенными, пакеты npm могут поставляться с этими модулями, которые находятся за хорошей абстракцией JavaScript. Это улучшает текущую экосистему и увеличивает повторное использование кода. Может наступить время, когда в создании собственных модулей WebAssembly нет необходимости.

Возможности безграничны с WebAssembly. Это инструмент, который вы можете добавить в свой арсенал сейчас — для устранения многих узких мест производительности, с которыми можно столкнуться в Интернете.