Работа с WebAssembly и C++: от А до Я

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в WebAssembly и C++

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

В этой статье мы пройдем путь от основ до практического применения: разберем, как компилировать C++ код в WebAssembly, запускать его в браузере и оптимизировать для максимальной производительности.

1. Инструментарий и настройка среды

Основной инструмент для компиляции C++ в WebAssembly — это набор компиляторов Emscripten. Он транслирует LLVM-байткод в Wasm и автоматически генерирует JavaScript-обвязку (glue code), необходимую для загрузки модуля.

Установка Emscripten

Рекомендуемый способ установки — через менеджер пакетов emsdk:

# Клонируем репозиторий
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk

# Устанавливаем и активируем последнюю версию
./emsdk install latest
./emsdk activate latest

# Добавляем переменные окружения (переменные оболочки)
source ./emsdk_env.sh

После установки у вас появятся команды emcc (компилятор) и em++ (компилятор C++), которые являются обертками над Clang.

2. Компиляция C++ в WebAssembly

Рассмотрим простейший пример. Создадим файл hello.cpp:

#include <emscripten/emscripten.h>

// Функция, доступная из JavaScript
extern "C" {
    int EMSCRIPTEN_KEEPALIVE add(int a, int b) {
        return a + b;
    }
}

Важный элемент — макрос EMSCRIPTEN_KEEPALIVE. Он предотвращает удаление функции оптимизатором и делает её экспортируемой.

Скомпилируем наш код:

em++ hello.cpp -o hello.html -s WASM=1 -s EXPORTED_FUNCTIONS='["_add"]' -O3

Разберем флаги:

  • -o hello.html — создает HTML-файл с примером запуска.
  • -s WASM=1 — явно указываем генерацию Wasm (включено по умолчанию).
  • -s EXPORTED_FUNCTIONS='["_add"]' — список экспортируемых функций (с префиксом _).
  • -O3 — высокий уровень оптимизации.

В результате получим три файла: hello.html, hello.js (glue code) и hello.wasm.

3. Взаимодействие с JavaScript

После компиляции мы можем вызвать нашу C++ функцию из JavaScript. Вот минимальный код загрузки модуля:

<!DOCTYPE html>
<html>
<head>
    <script>
        var Module = {};
        Module.onRuntimeInitialized = function() {
            // Функция add теперь доступна
            var result = Module._add(5, 7);
            console.log("Результат: " + result); // 12
        };
    </script>
    <script src="hello.js"></script>
</head>
<body></body>
</html>

Ключевые моменты:

  • Module.onRuntimeInitialized — событие, вызываемое после загрузки Wasm-модуля.
  • Имена функций в JS получают префикс _ (например, _add).
  • Для передачи сложных структур данных (строк, массивов) используются Module._malloc и указатели.

Передача строк

Пример функции, возвращающей приветствие:

// C++ код
extern "C" {
    const char* EMSCRIPTEN_KEEPALIVE greet(const char* name) {
        static std::string msg = "Hello, " + std::string(name) + "!";
        return msg.c_str();
    }
}

В JavaScript строку нужно преобразовать:

var ptr = Module.allocate(
    Module.intArrayFromString("World"), 
    'i8', 
    Module.ALLOC_NORMAL
);
var resultPtr = Module._greet(ptr);
var greeting = Module.Pointer_stringify(resultPtr);
console.log(greeting); // Hello, World!

4. Оптимизация и лучшие практики

Чтобы получить максимальную производительность от WebAssembly, следуйте этим рекомендациям:

Настройки компилятора

em++ main.cpp -o main.js -O3 -s WASM=1 \
    -s EXPORTED_FUNCTIONS='["_myFunc"]' \
    -s ALLOW_MEMORY_GROWTH=1 \
    -s TOTAL_MEMORY=67108864 \
    --closure 1
  • -O3 — агрессивная оптимизация.
  • --closure 1 — минификация JavaScript-обвязки через Google Closure Compiler.
  • ALLOW_MEMORY_GROWTH=1 — позволяет динамически расширять память (чуть медленнее, но гибче).
  • TOTAL_MEMORY — начальный размер памяти (по умолчанию 16 МБ).

Избегайте лишних вызовов

Каждый вызов функции из JS в Wasm имеет накладные расходы. Старайтесь:

  • Группировать операции в одну функцию (пакетная обработка).
  • Использовать EMSCRIPTEN_KEEPALIVE только для функций, вызываемых из JS.
  • Для работы с большими массивами данных используйте прямую работу с памятью через Module.HEAPU8.

Работа с памятью

Пример эффективной работы с массивом:

// C++: заполнение массива
extern "C" {
    void EMSCRIPTEN_KEEPALIVE fill_array(int* arr, int size, int value) {
        for (int i = 0; i <                

Блоги

Рекомендаций по книгам