Учебник по использованию WebAssembly с Emscripten и C / C ++ (даже если вы не знакомы с C / C ++)
JavaScript, который был классно разработан за 10 дней еще в 1995 году, до недавнего времени оставался единственным языком, который вы могли использовать изначально в Интернете. Только в 2017 году была выпущена WebAssembly 1.0, что сделало его вторым языком, поддерживаемым всеми основными веб-браузерами.
Что такое WebAssembly?
Как следует из названия, вы можете рассматривать WebAssembly как язык ассемблера для Интернета (хотя позже мы увидим, почему он также полезен вне браузера). Как и в случае с другими языками ассемблера, о которых вы слышали в CS101, на самом деле никто не пишет код непосредственно в WebAssembly, кроме Ben Smith 😉. Вместо этого мы, простые смертные, обычно используем его как цель компиляции, т.е. вы берете код, написанный на других языках (например, C, C ++ и Rust), компилируете этот код в WebAssembly и запускаете его в браузере .
О чем весь этот шум?
WebAssembly - это большое дело ™, потому что, в отличие от других технологий (таких как JVM), это стандарт, рожденный в результате сотрудничества между всеми основными поставщиками браузеров, и он был разработан с учетом модели веб-песочницы. Вот что делает WebAssembly таким привлекательным.
Кстати, именно поэтому WebAssembly может быть очень полезен вне браузера, о чем мы поговорим позже.
Давайте напишем WebAssembly!
В качестве нашей программы Hello World мы найдем готовый код на C для вычисления MD5-хэша строки, скомпилируем его в WebAssembly и запустим в браузере!
Шаг 1. Настройте среду
Emscripten - популярный инструмент, используемый для компиляции кода из C / C ++ в WebAssembly. Это мощный инструмент, потому что он предоставляет множество очень полезных функций, облегчающих нам жизнь при переносе кода на WebAssembly. Сюда входят оболочки для компиляторов C / C ++, автоматически сгенерированный шаблонный код JavaScript для запуска нашего кода WebAssembly, виртуальная файловая система (для кода, который должен читать / писать файлы) и доступ к множеству предварительно портированных библиотек (например, zlib или SDL).
Хотя установка Emscripten с нуля может занять некоторое время, я сделал образ Docker, который вы можете использовать, чтобы упростить этот процесс:
# Fetch image containing Emscripten $ docker pull robertaboukhalil/emsdk:1.38.30 # Create a local folder for our WebAssembly adventures $ mkdir ~/wasm $ cd ~/wasm # Launch a container called "wasm" (in the background) $ docker run -dt --name wasm \ --volume ~/wasm:/src \ -p 12345:80 \ robertaboukhalil/emsdk:1.38.30
Параметр --volume
позволяет нам запускать Emscripten внутри контейнера и извлекать сгенерированные .wasm
файлы непосредственно из ~/wasm
(т.е. нет необходимости передавать файлы из контейнера на хост!). Обратите внимание, что мы также предоставляем порт 80
контейнера как порт 12345
, и я настроил образ контейнера таким образом, чтобы он автоматически запускал веб-сервер, чтобы вы могли напрямую просматривать .html
файлы, выводимые Emscripten, как мы увидим ниже.
Уф, теперь мы готовы использовать WebAssembly! 😅
Шаг 2. Скомпилируйте код в WebAssembly
В нашем примере с хешем MD5 мы будем использовать это репо: https://github.com/pod32g/MD5.
Зайдем внутрь контейнера и получим код:
# Go inside the container we created above $ docker exec -it wasm bash # Now we're inside the container! $ git clone https://github.com/pod32g/MD5.git
Внутри папки MD5
вы увидите файл md5.c
:
$ ls README.md md5.c
Вы можете увидеть полный код здесь, но вот псевдокод, чтобы проиллюстрировать структуру кода:
# md5.c function md5(myString): return some_complicated_math(myString) function main(argv): # Expect user to provide a string as input: # argv[0] = name of executable # argv[1] = string to hash if length(argv) < 2: print("usage: %s 'string'\n", argv[0]) return 1 # Calculate and output the hash hash = md5(argv[1]) print(hash)
По сути, main()
выбирает строку ввода, предоставленную пользователем, и вызывает функцию md5()
для этой строки.
Отложив на секунду WebAssembly, если бы мы хотели скомпилировать md5.c
в старый добрый двоичный файл, мы бы сделали:
$ gcc -o md5 md5.c
Поскольку Emscripten предоставляет оболочки для таких инструментов, как gcc
, компиляция в WebAssembly выполняется просто:
$ emcc -o md5.html md5.c
Все, что мы сделали, это использовали оболочку Emscripten gcc
под названием emcc
(Emscripten также предоставляет оболочки для g++
, make
, cmake
и configure
, называемые em++
, emmake
, emcmake
и emconfigure
соответственно). Мы также запрашиваем у Emscripten .html
выходной файл вместо двоичного файла.
За кулисами Emscripten создает скомпилированный файл WebAssembly md5.wasm
, но он также генерирует md5.js
и md5.html
, которые представляют собой шаблонный код, который нам нужен для автоматической инициализации нашего модуля WebAssembly.
Шаг 3. Запуск нашего кода в браузере
Если вы следовали приведенным выше инструкциям по созданию контейнера wasm
, вы сможете запустить http: // localhost: 12345 / MD5 / md5.html в своем браузере и увидеть что-то вроде этого:
Чтобы протестировать наш новый инструмент, откройте консоль разработчика и введите следующее, чтобы вычислить хэш md5 строки test
:
> Module.callMain([ "test" ]) 098f6bcd4621d373cade4e832627b4f6
Module.callMain()
- служебная функция, предоставляемая связующим кодом Emscripten (md5.js
), которая вызывает функцию main()
нашей программы C со списком аргументов (в нашем случае только одним). В процессе callMain()
также преобразует строку test
в массив целых чисел (поскольку Wasm понимает только int и float!).
Шаг 4: простое приложение
Конечно, в реальной жизни мы не просим пользователя открыть консоль разработчика, так как же нам использовать WebAssembly в наших приложениях?
Вот простой app.html
файл, иллюстрирующий эту идею. Когда страница загружается, она запрашивает у пользователя строку, запускает на ней наш код WebAssembly и выводит пользователю хеш MD5. Для этого мы расширим объект Module
, о котором я упоминал выше - подробности выходят за рамки этой статьи, но комментарии должны дать вам представление о том, как это работает:
<!-- app.html --> <script type="text/javascript"> var Module = { // Don't run main() on page load noInitialRun: true, // When Wasm module is ready, ask user for a string onRuntimeInitialized: () => { let myString = prompt("Enter a string:"); Module.callMain([myString]); }, // Redirect stdout to alert() print: txt => alert(`The MD5 hash is: ${txt}`) }; </script> <script src="md5.js"></script>
Теперь вы можете запустить http: // localhost: 12345 / MD5 / app.html и хешировать интересующую строку:
И вуаля! Всего за несколько шагов мы перенесли готовую утилиту с C на WebAssembly.
Конечно, это был простой пример: у нас есть единственный файл C с простыми входными данными, в котором мы предоставляем только функцию main()
, не нуждаемся в поддержке файловой системы, никакой графики и никаких тяжелых вычислений. Тем не менее, надеюсь, этот пример дает вам представление о том, как код компилируется для WebAssembly.
⚠️ Осторожно: впереди бесстыдная затычка️ ⚠️
Если вы готовы погрузиться в WebAssembly более глубоко (и как портировать гораздо более сложные кодовые базы, такие как awk или Pacman ), посмотрите мою книгу Повышайте уровень с WebAssembly , практическое и доступное руководство по использованию WebAssembly в ваших собственных веб-приложениях 😊
Примеры использования в реальном мире
Теперь, когда мы ознакомились с нашим примером «Hello MD5 World», давайте рассмотрим реальные варианты использования WebAssembly.
1) Используйте WebAssembly, чтобы хирургическим путем ускорить работу ваших веб-приложений.
Иногда вы можете использовать WebAssembly, чтобы заменить медленные вычисления JavaScript и ускорить работу ваших веб-приложений. Это возможно, потому что WebAssembly является типизированным языком, имеет линейную структуру памяти и хранится в компактном двоичном формате, который быстрее загружается и интерпретируется, чем JavaScript.
Если библиотека, выполняющая необходимый анализ данных, уже существует и написана на C / C ++ / Rust, WebAssembly - очевидный выбор. В таких случаях не стоит тратить усилия на перенос кода на JavaScript в первую очередь (а затем переходить к его проверке и оптимизации!).
Ресурсы:
- Пример использования: приложение 1Password добилось ускорения на порядок за счет использования WebAssembly.
- Пример из практики: как мы ускорили веб-приложение в 20 раз, заменив медленные вычисления JavaScript на WebAssembly
- Сообщение в блоге: Обсуждение накладных расходов на запуск кода WebAssembly и почему мы должны быть осторожны с микротестами.
2) Перенос десктопных приложений и игр на WebAssembly
Хотя иногда можно обойтись без внесения изменений в кодовую базу для ее переноса на WebAssembly (как в случае с нашим примером MD5), вам почти всегда придется это делать, когда дело касается игр и других графических приложений. Это связано с тем, что, хотя большинство игр написаны как бесконечные циклы, которые ждут ввода пользователя (перемещения мыши, нажатия клавиши и т. Д.), Бесконечные циклы плохо работают в браузере, где они блокируют основной поток и могут привести к сбою вкладка браузера. К счастью, Emscripten предоставляет множество полезных функций, позволяющих обойти это ограничение, которые вы можете использовать для симуляции бесконечных циклов, вызывая одну и ту же функцию с регулярным интервалом.
Ресурсы:
- Практический пример: как AutoCAD использовал WebAssembly для переноса своей 30-летней кодовой базы в Интернет без необходимости переписывать все с нуля 😅
- Демо: воспроизвести в браузере клон Doom3, созданный с помощью WebAssembly.
- Учебник: Как перенести в Интернет простую игру Asteroids (написанную на C)
3) Перенос инструментов командной строки в Интернет
Помимо переноса графических приложений, мы также можем использовать WebAssembly для переноса инструментов командной строки в Интернет! Зачем ты вообще это делал? Одна из причин - повторно использовать существующую функцию, которая уже эффективно реализована (например, если вы хотите показать различия между двумя файлами, вы можете повторно использовать алгоритм сравнения, включенный в инструмент командной строки diff). Или вы можете создать интерактивную игровую площадку, которая позволит пользователям протестировать (и изучить) инструмент без предварительной его установки.
Ресурсы:
4) WebAssembly вне браузера
Несмотря на Интернет в названии, полезность WebAssembly выходит далеко за рамки браузера. Фактически, вы можете использовать его как JVM-подобный способ запуска .wasm
двоичного файла на любой платформе, при условии, что платформа поддерживает среду выполнения WebAssembly, такую как Wasmtime или Wasmer.
Но если мы хотим продуктивно запускать код WebAssembly вне браузера, как мы поддерживаем такие функции, как файлы, потоки и сокеты? Ответ заключается в том, что нам нужен какой-то стандартный интерфейс, который WebAssembly может использовать для связи с внешним миром для запроса ресурсов. Чтобы решить эту проблему, ведется постоянная работа по стандартизации системного интерфейса WebAssembly (или WASI), цель которой - сделать это возможным.
Мы все еще только начинаем запускать WebAssembly вне браузера (о WASI было объявлено всего несколько месяцев назад, в марте 2019 года), но, как вы увидите в ресурсах ниже, мы живем в захватывающие времена!
Ресурсы:
- Запись в блоге: подробное знакомство с WASI, как это работает и почему это полезно
- Демо: запускать двоичные файлы WebAssembly из командной строки с помощью Wasmer.
- Учебник: использование библиотеки Wasmer's Go для выполнения программ WebAssembly
5) Бессерверная веб-сборка
Другой вариант использования WebAssembly вне браузера - это использовать его как часть бессерверной архитектуры / архитектуры «функция как услуга». Одно из преимуществ состоит в том, что, поддерживая WebAssembly, облачные провайдеры будут косвенно поддерживать гораздо больше языков, включая C, C ++ и Rust, которые обычно напрямую не поддерживаются бессерверными облачными провайдерами. Более того, изолированная природа WebAssembly означает, что он более удобен для параллельного запуска нескольких независимых модулей от разных клиентов в одном процессе / контейнере, что приводит к гораздо более быстрому времени инициализации функции.
Ресурсы:
- Учебник: бессерверный Rust с AWS Lambda и WebAssembly
- Учебное пособие: запуск бессерверной WebAssembly на воркерах Cloudflare
- Сообщение в блоге: Как быстро запускает WebAssembly на периферии, чтобы значительно улучшить время выполнения
Заключительное слово
Хотя мы все еще находимся на ранних стадиях, WebAssembly уже используется в реальных приложениях, будь то перенос целых интерфейсов командной строки / игр / настольных приложений в Интернет, хирургическое применение его для ускорения вычислений внешнего интерфейса или даже его использование. вне браузера. В ближайшем будущем WebAssembly получит поддержку потоков, инструкций SIMD, сборки мусора и возможности прямого управления DOM, и это лишь некоторые из них.
Конечно, не всегда имеет смысл использовать WebAssembly - я знаю, это шокирует, - поэтому вам следует оценить, стоят ли преимущества, которые он приносит, дополнительной сложности.
Я надеюсь, что эта статья сделала WebAssembly немного менее похожим на волшебство 🧙 и немного больше похожим на мощный инструмент в вашем наборе инструментов.
Если вам понравилось это читать и вы хотите получить практическое руководство по началу работы с WebAssembly, ознакомьтесь с моей книгой Повышение уровня с помощью WebAssembly!