Эта статья основана на моём выступлении на конференции ITSubbotnik, прошедшем 2 ноября 2019 года в Москве.
Вообще я бэкенд программист, но меня заинтересовала эта технология, она позволяет использовать мои знания бэкенда на фронте.
Проблема
Начнём с проблемы, которая решается этой (относительно новой) технологией. Проблема эта — быстро исполнять код в браузере. Быстро — это значит, «быстрее чем JavaScript», в идеале настолько быстро, насколько позволяет имеющийся у нас процессор.
Кроме того, исторически, вокруг этой проблемы постепенно возникли важные дополнительные требования:
- Zero configuration — это должно быть решение «из коробки», ничего кроме браузера.
- Безопасно — новая технология не должна создавать новых угроз, у нас и так есть чем заняться по части безопасности.
- Кросс-платформенно — браузеры работают на всех основных процессорах, включая мобильные платформы.
- Удобно для разработчиков — то есть для нас с вами.
История до Wasm
Мы видели множество вариантов исполнения кода в браузере. Можно сказать, что на этом поле есть победители и проигравшие.
Победители: это, безусловно, JavaScript; движок V8, сделавший JS таким быстрым; а также HTML5.
Проигравшие: ActiveX — если вы помните, эта технология позволяла делать с машиной вообще всё что угодно, т.е. с безопасностью было очень плохо; Flash — сейчас мы наблюдаем эпоху заката Flash, хотя внутри него работает ActionScript, по сути, тот же JavaScript; Silverlight — наверное, он появился слишком поздно, чтобы занять серьёзную нишу.
В итоге, проиграли все плагины, в том числе из-за проблем с безопасностью.
Были и другие попытки решения проблемы, уже в браузере:
- NaCl — Native Client, предложенный Google; родной код прямо в браузере, что конечно ударяет по кросс-платформенности; Mozilla не поддержала эту инициативу, в итоге реализация была только в Chrome.
- PNaCl — Portable Native Client — в качестве переносимого кода использовалось подмножество LLVM IR; также не было поддержано в Mozilla, опять же только Chrome. В итоге, Google отказалась от поддержки PNaCl в мае 2017 года.
asm.js
asm.js — ещё одна интересная инициатива, уже от Mozilla Foundation, которая подводит нас вплотную к теме WebAssembly. Появилась она в 2010 году, а в 2013 стала публично доступна.
asm.js это подмножество JavaScript, в него можно компилировать код из C и C++ с помощью компилятора Emscripten.
Поскольку это тоже JavaScript, то такой код будет исполняться в любом браузере. Кроме того, основные современные браузеры уже давно умеют быстро распознавать asm.js и эффективно компилировать его в родной код процессора. В сравнении с родным кодом, полученным непосредственно из C/C++, код полученный из asm.js медленнее всего в 1,5-2 раза (50-67 %).
Слева здесь код простейшей функции на C/C++, справа показано во что она превращается после компиляции в asm.js. Во-первых, мы здесь видим строку 'use asm'
, это маркер, дающий понять, что дальше идёт код на asm.js. И в этом коде мы видим конструкции вида |0
, это побитовая операция ИЛИ с нулевым значением. Согласно спецификации, результатом этой операции является 32-разрядное целое со знаком. Но такого типа даже нет в JavaScript. Тем не менее, такой тип возникает в результате этой операции, т.е. это по сути это приведение значения к заданному типу.
В целом, asm.js это использование наших знаний о том, как браузерный движок компилирует JS, для того чтобы оптимизировать эту работу.
Что же такое WebAssembly
WebAssembly (или Wasm) — это бинарный формат, запускаемый в браузере, виртуальная машина, и результат компиляции с языка высокого уровня.
Wasm это не язык программирования, подобно тому как байт-код Java это не язык программирования, а результат компиляции и запускаемый блок кода.
Кто-то очень умный сказал, что название web assembly (то есть «ассемблер для веба») полностью неправильное, потому что это не ассемблер (не язык программирования) и он никак не связан с вебом (потому что это просто виртуальная машина).
Создатели WebAssembly руководствовались следующими целями и ограничениями — см. видео Evolving Wasm into a proper misnomer: Andreas Rossberg.
По сути, они сводятся к трём вещам — кросс-платформенность, компактность, скорость. Но было ещё одно важное требование, это «продаваемость» — инициативу должны были воспринять и подхватить разработчики основных браузеров. В итоге это удалось «продать», в разработке спецификации прияли участие представители Google, Mozilla, Microsoft и Apple.
Посмотрим, что представляет из себя Wasm как виртуальная машина.
Это такой «выдуманный процессор», но без регистров, всё делается через стек. Всего четыре типа данных: два целых, два плавающих. Относительно простой набор операций — см. спецификацию и интерактивную таблицу.
Плоская модель памяти: под память выделяется единый блок, размер которого кратен 64 КБ. Здесь находится код, данные, константы, глобальные переменные, стек растущий вниз, куча растущая вверх. Можно сделать так, чтобы куча автоматически увеличивалась при необходимости, при этом блок памяти расширяется на размер кратный 64 КБ.
Указатели не используется (это сделано для безопасности), вместо этого используется индекс. Индекс 32-разрядный, поэтому адресуется до 4 ГБ памяти.
Вся память WebAssembly полностью доступна из JavaScript, причём как на чтение, так и на запись.
Посмотрим на модель исполнения WebAssembly. Wasm всегда загружается и вызывается ТОЛЬКО из JavaScript. Более того, JS и Wasm работают в одной и той же «песочнице», и исполняются одним и тем же движком.
Заметим, что из Wasm также можно вызывать JS. Это может быть вызов функции с передачей аргументов и возвратом значения, либо это может быть просто выполнение произвольной строки как JS-кода.
Пробуем WebAssembly
Для того чтобы освоиться с WebAssembly, я рекомендую воспользоваться сайтом WasmFiddle или WebAssembly Studio — это простой и наглядный способ понять для себя, что такое Wasm на самом деле.
Здесь слева вверху исходный код на C/C++ (в данном случае, рекурсивная функция расчёта числа Фибоначчи), справа вверху код на JS для загрузки Wasm, инстанциирования Wasm-модуля и вызова из него функции fib()
. Когда мы нажимаем кнопку Build, то получаем блок кода Wasm (.wasm файл), который мы всегда можем развернуть в текстовое представление (.wat файл).
Это текстовое представление с кучей скобочек — по сути, абстрактное синтаксическое дерево (AST). Ну и собственно скобочной записью это напоминает язык Lisp. По идее, мы можем редактировать код в виде текстового представления, и затем свернуть его вновь в бинарный формат — это напоминает программирование на языке ассемблера. Но обычно мы получаем бинарный Wasm в результате компиляции с языка высокого уровня.
2017 год: Production Ready
В ноябре 2017 года WebAssembly был объявлен «готовым к использованию в продакшене». Спецификация на все основные части Wasm была подготовлена, вышла реализация Wasm во всех основных браузерах. Тем самым, для WebAssembly был «выпущен» MVP — Minimum Viable Product, версия 1.0, с которой мы и имеем дело сейчас.
Поддержка в браузерах
В конце 2017 года были выпущены релизы всех основных браузеров с поддержкой WebAssembly:
Исключение — IE11, для него поддержки нет, и по всей видимости, уже не будет. Предполагалось, что для старых браузеров будет polyfill — возможность преобразования Wasm в asm.js; такие прототипы есть, но насколько я видел, эти проекты заброшены, видимо, сообществу не до них.
Сейчас среди всех установленных браузеров, ~88% поддерживают Wasm.
Поддержка языков
Для того, чтобы ваш любимый язык компилировался в Wasm, нужно чтобы компилятор обеспечивал такую цель компиляции. Сейчас уже довольно много языков поддерживают Wasm, и их становится всё больше с каждым месяцем. См. appcypher/awesome-wasm-langs.
- C/C++ — через Emscripten, очень хорошая поддержка.
- Rust — поддержка Wasm появилась довольно давно, экосистема вокруг Wasm строится во многом на основе Rust.
- Java — через TeaVM, JWebAssembly, Bytecoder — на экспериментальном уровне.
- Kotlin — есть поддержка в Kotlin/Native через LLVM backend — экспериментальная.
- Go
- C# — через Blazor (mono) и в будущем на Uno Platform.
- TypeScript — через AssemblyScript.
Не так давно появилась новость, что LLVM теперь поддерживает Wasm как цель компиляции. Это облегчает работу для разработчиков языков программирования, мы получим ещё больше языков, компилирующих в Wasm.
Сценарии использования
Наверное, это один из главных вопросов — «А как я могу использовать Wasm?»
Уже сейчас WebAssembly активно применяется:
- Игры, игровые движки, движки физики, VR/AR — например, Godot, Doom 3
- Эмуляторы, виртуальные машины — например, DOSBox
- Графические/3D редакторы — Figma, AutoCAD
- Веб-клиенты для финансовых торговых площадок — я видел презентации про два таких клиента
- Кодеки и фильтры аудио/видео — например, ffmpeg
- Базы данных — например, sqlite
Другие возможные сценарии:
- Progressive web applications (PWA)
- Распознавание натренированной нейронной сетью
Производительность
Производительность Wasm была одним из его главных «продающих» факторов, но что с ней происходит на самом деле?
В сравнении с JavaScript, получается, что в среднем Wasm быстрее, но в каждом частном случае нужно делать сравнение JS/Wasm, потому что может получиться и во много раз лучше, и в несколько раз хуже. Также это может сильно зависеть от используемого браузера.
На самом деле, пиковая производительность JS и Wasm одинакова, поскольку оба в итоге превращаются в родной код процессора. Но JS гораздо легче теряет в производительности, а Wasm обеспечивает более «ровный» подход.
Как правило, Wasm хорошо показывает себя на объёмных вычислениях. Там где много операций с памятью, Wasm проигрывает. Ну и основная проблема в реальных применениях — это медленный интероп JS <-> Wasm. См. например, бенчмарк.
В июле 2019 года вышла научная статья «Not So Fast: Analyzing the Performance of WebAssembly vs. Native Code». Авторы реализовали возможность запуска под WebAssembly консольных утилит Linux, для запуска бенчмарков, и использовали бенчмарки SPEC для оценки производительности Wasm по сравнению с теми же тестами на asm.js и на родном коде.
Результаты такие:
- Wasm на 30% быстрее, чем JavaScript (в среднем, на этих тестах)
- Wasm на 50% медленнее, чем родной код (в среднем, на этих тестах)
Авторы статьи также дали анализ причин, на чём именно Wasm «подтормаживает»:
- примерно вдвое больше операций загрузки/сохранения данных, по сравнению с родным кодом;
- больше ветвлений — вызвано необходимостью дополнительных проверок при обращении к памяти;
- больше «промахов» мимо кеша L1.
В общем, на самом деле, с производительностью всё не так плохо. К тому же, этот анализ позволит разработчикам браузеров сделать Wasm ещё быстрее.
В будущем нас ожидает ускорение Wasm не только за счёт лучшей оптимизации в браузерах, но и за счёт новых фич, таких как: блоковые операции над памятью, поддержка SIMD-инструкций, поддержка threads.
Что будет дальше?
Как развивается WebAssembly?
Во-первых, группа, работающая над спецификациями для Wasm, продолжает свою работу. Спецификации находятся на разных этапах (фазах), есть определённая «дорожная карта» этой работы.
В частности, в ближайшее время мы ожидаем дальше увидеть такие фичи:
- Non-trapping float-to-int conversions — сейчас конвертация из плавающего значения в целое в некоторых условиях может вызвать исключение; для программиста это довольно неожиданно, поэтому все такие конвертации приходится оборачивать, теряя на этом производительность. Эта фича решает проблему.
- Multi-value — возможность возврата больше одного значения из функции, возможность создания новых инструкций, возвращающих более одного значения — например, результат деления и остаток от деления, или результат сложения и бит переноса.
- Reference Types — введение типа anyref, обозначающего «ссылка на что-то в куче JS», первый шаг к тому чтобы ускорить взаимодействие с JS.
Во-вторых, разработчики браузеров, со своей стороны, реализуют эти спецификации, т.е. постепенно к Wasm добавляются новые фичи, сначала скрытые «под флагом» в настройках, а затем и включенные по умолчанию.
Вот, например, список фич Chrome, относящихся к WebAssembly.
Для Firefox подобный список можно найти здесь.
WebAssembly вне браузера
Как было сказано выше, Wasm по сути никак не связан с вебом, это просто виртуальная машина. А значит, его вполне можно использовать и вне веба.
Сейчас видны несколько сценариев использования Wasm вне браузера:
- Node.js — в основе Node.js лежит движок V8, который поддерживает Wasm.
- Отдельное консольное приложение, код приложения исполняется в Wasm VM — примеры таких runtime: wasmtime, wasmer.
- Wasm VM используется как библиотека из других языков — например, wasmer позволяет вызывать себя из десятка различных языков.
Для Wasm работающего вне браузера, уже не нужны ограничения «песочницы», напротив, необходим доступ к функциям системы — файловая система и файлы, консольный ввод/вывод и т.д. Это привело к созданию WebAssembly System Interface (WASI) — спецификации кросс-платформенного API, подобного POSIX. См. WebAssembly/WASI и wasi.dev.
Следующим шагом стало создание менеджера пакетов — Wasm Package Manager (WAPM) — warp.io. Здесь вы можете взять готовый .wasm-файл и использовать его в своём приложении. Обычно тут речь идёт о Wasm-версиях каких-то известных библиотек. Часть пакетов помечено тэгом «WASI», что означает — их можно использовать только в сценариях работы вне браузера.
Заключение
Итак, WebAssembly вполне можно использовать, он уже два года как «production ready».
Применение Wasm вполне может дать некоторое ускорение, по сравнению с аналогичным кодом на JavaScript, но всегда нужно проверять, получился ли прирост скорости.
Поддержка Wasm со стороны языков программирования постоянно развивается.
Ну и самое главное, WebAssembly несколько «изменил ландшафт» веба — предоставил нам новые сценарии использования, которые мы можем реализовать в своих приложениях.
Ссылки
Awesome списки:
Видео:
- WebAssembly for Web Developers (Google I/O ’19)
- What is WebAssembly? By Some of its Creators
- Evolving Wasm into a proper misnomer: Andreas Rossberg
- C++ Russia 2018: Павел Булатов, Переход на WebAssembly: стоит ли игра свеч?
- Андрей Нагих — Разработка под WebAssembly: реальные грабли и примеры
- Сергей Рубанов — Понятно о WebAssembly
- WebAssembly in the Browser and Beyond by Dan Callahan | Mozilla Developer Roadshow EU 2019
Автор: nzeemin