В этой истории я расскажу о подходе, который я использовал для понимания WebAssembly, и о том, как я сделал первый удар, построив простую библиотечную функцию конкатенации строк на C ++, скомпилировав ее в WASM (W eb A s S e M bly), импортировал его как модуль JavaScript и запустил с помощью NodeJS.

Это правильно! Вы можете компилировать функции C / C ++ / Rust с помощью WebAssembly, а также импортировать и вызывать их из модуля JavaScript. И именно по этой причине я считаю, что WebAssembly изменит способ создания приложений в Интернете.

Что такое WebAssembly?

WebAssembly (сокращенно Wasm) - это двоичный формат инструкций для виртуальной машины на основе стека. Wasm разработан как переносимая цель для компиляции языков высокого уровня, таких как C / C ++ / Rust, что позволяет развертывать клиентские и серверные приложения в Интернете ».

Ключевой вывод из этого определения заключается в том, что оно обеспечивает «переносимую цель для компиляции C / C ++ / Rust». Что это значит?

Для того, чтобы это понять. Поразмыслим над другим вопросом. Вы когда-нибудь задумывались, почему приложения с интенсивными вычислениями, такие как удаленный рабочий стол, игры, AR / VR, редактирование изображений / видео, приложения САПР и т. Д., Еще не получили хорошего опыта в браузере?

Большинство этих приложений очень чувствительны ко времени. В настоящее время они устанавливаются как настольные приложения и обеспечивают гораздо лучший опыт по сравнению с тем, что создано для браузера. Также потому, что довольно сложно добиться такой же производительности с использованием JavaScript, и в настоящее время движки браузера не предоставляют цели компиляции (например, x86 или ARM), даже если вы хотите перенести их изначально с использованием C / C ++. WebAssembly находится именно в этой точке трения. Он обеспечивает цель компиляции в браузере для запуска приложений C / C ++ / Rust.

EMSCRIPTEN

Итак, когда мы говорим о новой цели компиляции, нам, очевидно, нужен новый компилятор. А новый компилятор, который компилирует C / C ++ / Rust для целевого браузера, называется Emscripten.

Emscripten - это компилятор LLVM в JavaScript с открытым исходным кодом. Используя Emscripten, вы можете:

  • Скомпилируйте код C и C ++ в JavaScript.
  • Скомпилируйте любой другой код, который можно преобразовать в битовый код LLVM, в JavaScript.
  • Скомпилируйте среды выполнения других языков C / C ++ в JavaScript, а затем запустите код на этих других языках косвенным способом (это было сделано для Python и Lua).

Вы можете узнать больше о Emscripten и его внутренней работе на официальной веб-странице. Но если вы просто хотите скомпилировать C / C ++ для целевого браузера, достаточно изучить инструкции по компиляции и различные параметры командной строки. Чтобы установить компилятор emcc, следуйте инструкциям отсюда: Установить emcc.

Базовая команда компиляции выглядит так:

emcc string_concat.cpp -Os -s WASM=1 -s “EXPORTED_FUNCTIONS=[‘_malloc’]” -o module.js -std=c++11 -s ASSERTIONS=1
  • «emcc» вызывает компилятор emscripten, за которым следует исходный файл «string_concat.cpp».
  • Параметр -0 используется для установки уровней оптимизации.
  • WASM = 1 сообщает компилятору, что это сборка WASM.
  • EXPORTED_FUNCTIONS сообщает компилятору, что мы хотим сделать доступным из скомпилированного кода (все остальное может быть удалено, если оно не используется).
  • -o module.js компилирует его в модуль JavaScript.
  • -std = c ++ 11 сообщает компилятору, что это код C ++ 11.
  • ASSERTIONS = 1 - необязательный аргумент, который предоставляет дополнительную информацию, если что-то не удается во время выполнения сборки.

Компилятор EMSCRIPTEN все еще развивается, и последняя информация редко доступна. Но в разделе FAQ официальной веб-страницы есть достаточно информации, чтобы начать работу, если вы хотите поиграть с различными параметрами компилятора.

Управление памятью

В JavaScript память отвлекается от вас. Это означает, что вы не трогаете память напрямую. Вместо этого движок JS действует как посредник. Он управляет памятью за вас.

В C / C ++ нет того уровня абстракции, который используется в JavaScript для памяти. Вместо этого вы работаете непосредственно с памятью. Вы можете загружать вещи из памяти, а можете сохранять вещи в памяти.

Итак, когда мы компилируем код C ++ в WebAssembly и вызываем его с помощью JavaScript, нам необходимо соответствующим образом управлять памятью для нашего кода C ++ и кода JS, который вызывает скомпилированный код C ++. Чтобы быть точным, нам нужна память, которая используется совместно JS и WebAssembly, чтобы они могли общаться друг с другом. Когда мы пишем собственный код C ++ для x86 или ARM, у нас есть доступ к куче и стековой памяти. С WebAssembly доступ к памяти затруднен. Это связано с тем, что предоставление кода C ++ полного доступа к памяти имеет последствия для безопасности. Вместо этого для этой цели выделяется часть памяти. В отличие от памяти кучи, это линейный массив адресов памяти.

Эта память представлена ​​в JS с помощью TypedArrays. Если вы хотите загрузить переменные, создайте TypedArray, загрузите его с переменными. Этот TypedArray можно напрямую загрузить в память с помощью API WebAssembly, _ malloc и .set

Пример - конкатенация строк

Теперь, когда все основы позади, давайте рассмотрим пример. Я написал простую функцию .cpp для объединения двух строк.

EMSCRIPTEN_KEEPALIVE - это макрос, который заставляет LLVM не устранять мертвый код функции, которой в данном случае является concat (). Остальной код довольно легко понять. Функция принимает два указателя на каждую входную строку и размеры этих двух строк в качестве параметров. Затем функция возвращает указатель на вновь объединенную строку. Довольно просто!

Теперь давайте скомпилируем эту функцию, используя следующую команду:

emcc string_concat.cpp -Os -s WASM=1 -s "EXPORTED_FUNCTIONS=['_malloc']" -o module.js -std=c++11  -s ASSERTIONS=1

После выполнения этой команды мы получаем два файла: module.wasm и module.js. Больше всего нас беспокоит module.js. Module.js экспортирует Module, в котором есть все товары, необходимые для вызова функции C ++. Теперь давайте напишем простой JS-код, который вызывает нашу функцию concat ().

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

  • Преобразуйте наши входные строки в символьные массивы. [Строки 9–18]
  • Преобразуйте массив символов в массив значений ASCII, соответствующих символам [Строки 9–18].
  • Выделите место в нашей общей памяти для этих двух массивов и загрузите в них значения массивов ASCII. [Строки 33–37]
  • Передайте массивы ASCII функции concat (). [Строка 41]
  • Получите возвращаемое значение и преобразуйте его обратно в строку. [Строки 47–52]

Вот и все! Теперь мы создали функцию конкатенации строк, написанную с использованием C ++, которую можно вызывать внутри модуля JS. Что касается прироста производительности, WebAssembly, безусловно, дает огромный импульс, поскольку мы изначально компилируем для Интернета. Вы можете найти весь исходный код здесь:

https://github.com/kakaly/webassembly-benchmarking

Вот список ресурсов и ссылок, которые я считаю очень полезными:

WebAssembly

Emscripten

Производительность

Средние сообщения:

📝 Прочтите этот рассказ позже в Журнале.

🗞 Просыпайтесь каждое воскресенье утром и слышите самые интересные истории, мнения и новости недели, ожидающие в вашем почтовом ящике: Получите заслуживающий внимания информационный бюллетень›