Введение: Зачем WebAssembly в PHP?
В мире веб-разработки PHP остается одним из самых популярных языков для серверной части. Однако, когда речь заходит о высокопроизводительных вычислениях — обработке изображений, сложной математике, компиляции кода на лету или использовании кода на C/C++/Rust, — нативный PHP часто уступает. Именно здесь на помощь приходит WebAssembly (WASM).
WebAssembly — это бинарный формат инструкций, который выполняется с почти нативной скоростью. Традиционно он ассоциируется с браузерами, но современные рантаймы (Wasmer, Wasmtime) позволяют запускать WASM-модули на сервере, в том числе из PHP. В этой статье мы рассмотрим продвинутые техники интеграции WebAssembly в PHP-приложения: от настройки окружения до создания сложных модулей и оптимизации производительности.
1. Настройка окружения: Wasmer и PHP Extension
Для работы с WebAssembly в PHP нам потребуется специальное расширение. Самый зрелый и документированный вариант — wasmer-php.
Установка Wasmer
Сначала установите сам рантайм Wasmer:
curl https://get.wasmer.io -sSfL | sh
# Или через пакетный менеджер (macOS/Linux)
brew install wasmer
Установка PHP-расширения
Расширение можно установить через PECL или собрать вручную:
# Через PECL
sudo pecl install wasmer
# Или сборка из исходников (для продвинутых)
git clone https://github.com/wasmerio/wasmer-php.git
cd wasmer-php
phpize
./configure
make
sudo make install
После установки добавьте extension=wasmer.so в ваш php.ini и перезапустите сервер. Проверьте работоспособность:
<?php
echo class_exists('\\Wasmer\\Instance') ? 'WASM Ready' : 'Not installed';
?>
2. Загрузка и выполнение WASM-модуля из PHP
Допустим, у нас есть скомпилированный модуль math.wasm, который экспортирует функцию add(a, b) (написан на Rust или C). Загрузим его и вызовем из PHP.
Базовый пример: вызов функции
<?php
use Wasmer\Instance;
use Wasmer\Module;
use Wasmer\Memory;
use Wasmer\Value;
// 1. Загружаем модуль
$wasm_bytes = file_get_contents(__DIR__ . '/math.wasm');
$module = new Module($wasm_bytes);
// 2. Создаем экземпляр (инстанс)
$instance = new Instance($module);
// 3. Получаем экспортированную функцию
$add = $instance->exports->get('add');
// 4. Вызываем с аргументами
$result = $add(5, 7);
echo "Результат: " . $result->toInt(); // Выведет: 12
?>
Важно: WASM-функции работают с простыми типами (i32, i64, f32, f64). Для сложных структур (строки, массивы) требуется передача через линейную память (Memory).
Работа с памятью: передача строк
Если ваш WASM-модуль ожидает строку, нужно записать её в общую память и передать указатель:
<?php
// Предположим, WASM-функция 'greet' принимает указатель на строку (i32)
$memory = $instance->exports->get('memory');
$greet = $instance->exports->get('greet');
// Пишем строку в память (с нулевым байтом в конце)
$input = "Hello from PHP!";
$ptr = 100; // произвольный адрес, должен быть свободен
$memory->write($ptr, $input . "\0");
// Вызываем функцию
$result_ptr = $greet($ptr);
// Читаем результат из памяти
$output = '';
$offset = $result_ptr->toInt();
while (($byte = $memory->read($offset, 1)) !== "\0") {
$output .= $byte;
$offset++;
}
echo $output; // Приветствие из WASM
?>
3. Сборка собственного WASM-модуля (Rust → PHP)
Для максимальной производительности часто используют Rust. Рассмотрим минимальный пример.
Код на Rust
Создайте новый проект:
cargo new --lib wasm_math
cd wasm_math
Файл Cargo.toml:
[lib]
crate-type = ["cdylib"]
[dependencies]
Файл src/lib.rs:
#[no_mangle]
pub extern "C" fn factorial(n: u32) -> u64 {
if n == 0 { return 1; }
(1..=n).product::<u64>()
}
Сборка под WASM:
rustup target add wasm32-unknown-unknown
cargo build --release --target wasm32-unknown-unknown
Готовый файл будет лежать в target/wasm32-unknown-unknown/release/wasm_math.wasm.
Вызов из PHP
<?php
$module = new Module(file_get_contents('wasm_math.wasm'));
$instance = new Instance($module);
$factorial = $instance->exports->get('factorial');
echo $factorial(10)->toInt(); // 3628800
?>
4. Продвинутые техники: оптимизация и кэширование
Кэширование скомпилированных модулей
Компиляция WASM-байткода в машинный код — ресурсоемкая операция. Используйте кэширование:
<?php
$cache_dir = '/tmp/wasm_cache/';
$module_key = md5_file('math.wasm');
$cached_path = $cache_dir . $module_key . '.wasm_cache';
if (file_exists($cached_path)) {
// Загружаем из кэша (предварительно скомпилированный)
$module = Module::deserialize(file_get_contents($cached_path));
} else {
$module = new Module(file_get_contents('math.wasm'));
// Сериализуем и сохраняем
file_put_contents($cached_path, $module->serialize());
}
$instance = new Instance($module);
?>
Это ускоряет последующие вызовы в 5-10 раз.