Скомпилировать PHP в нативный бинарник без интерпретатора
Лет пятнадцать назад Facebook пытался решить проблему производительности своего монолита компилятором HPHPc, который перегонял PHP в C++. Позже они бросили эту затею ради виртуальной машины HHVM, а в официальном PHP 8 появился встроенный JIT. Казалось, идея чистого Ahead-Of-Time компилятора для PHP окончательно ушла в историю. Команда Swoole решила к ней вернуться и выкатила проект TypePHP.
Это полноценный AOT-компилятор, написанный на самом PHP. Он транслирует исходный код на PHP 8.4+ в C++17, а затем собирает его через GCC или Clang в нативный машинный код. Никаких опкодов во время исполнения, никакого прогрева JIT и никакого интерпретатора в рантайме.
При этом компилятор полностью self-hosting. Утилита tpc собирает сама себя из исходников на PHP, без C-клеев внутри самого компилятора.
Как устроен TypePHP
Компиляция проходит в два этапа. Сначала анализатор парсит код проекта, собирает метаданные классов и функций, строя полную таблицу символов. На втором этапе тела функций транслируются в C++17. В горячих путях компилятор генерирует быстрый статический C++, а для динамических конструкций вроде рефлексии или вызова системных функций связывается с рантаймом Zend через прослойку PHPX.
На выходе можно получить один из трех форматов:
- Исполняемый бинарник (
bin). Автономный файл для консольных утилит и демонов. Ему нужна точка входаmain(). - Расширение для PHP (
ext). Обычный.soили.dllмодуль, который можно подключить в стандартныйphp.ini. - Динамическая библиотека (
lib). Бинарный файл со сгенерированными.stub.phpфайлами для вызова из других проектов. - WASI компонент для запуска в WebAssembly окружении.
Что меняется в синтаксисе
TypePHP не пытается поддерживать вообще все динамические трюки стандартного PHP. Создатели сделали ставку на строгую типизацию там, где нужна предельная скорость.
Нативные скалярные типы
Директива use native_types указывает компилятору отображать int, float и bool напрямую в C++ типы (int64_t, double, bool). Переменная с таким типом уже не сможет внезапно сменить тип посреди выполнения функции. Зато процессор крутит чистые машинные инструкции без распаковки zval.
<?php
use native_types;
function fib(int $n): int
{
if ($n == 1 || $n == 2) {
return 1;
}
return fib($n - 1) + fib($n - 2);
}
function main(int $argc, array $argv): void
{
$n = (int)$argv[1];
$begin = microtime(true);
echo fib($n) . "\n";
echo "Time: " . (microtime(true) - $begin) . "\n";
}
Собирается это одной командой:
bin/tpc.php fib.php -O3 -o fib
./fib 35
Строго типизированные контейнеры
Обычные массивы PHP универсальны, но прожорливы по памяти и медлительны из-за хеш-таблиц. TypePHP добавляет структуры std::vector, std::array, std::map и std::ordered_map.
<?php
use native_types;
function main(): void
{
$vector = std::vector(Type::Int);
$vector[] = 10;
$vector[] = 20;
$vector[] = 30;
$sum = 0;
foreach ($vector as $val) {
$sum += $val;
}
echo "Sum: " . $sum . "\n";
$map = std::ordered_map(Type::String, Type::Int);
$map["alpha"] = 1;
$map["beta"] = 2;
}
В тестах разработчиков цикл обновления элементов в std::array отработал за 6.4 секунды против 67.6 секунд у обычного PHP массива с включенным JIT. Скорость фактически сравнялась с рукописным C++ вектором (6.2 секунды).
Методы на примитивах
Вместо россыпи функций вроде strlen(), strtoupper() или in_array() можно вызывать методы прямо у базовых типов. Компилятор разрешает такие вызовы на этапе сборки и преобразует их в прямые вызовы Си-функций без накладных расходов на виртуальные таблицы:
<?php
function main(): void
{
$str = "hello world";
echo $str->upper() . "\n";
echo $str->substr(0, 5) . "\n";
$items = [1, 3, 5, 7];
var_dump($items->contains(3));
}
Генерация шаблонного кода атрибутами
Чтобы не писать руками десятки геттеров и сеттеров, TypePHP обрабатывает собственные атрибуты во время компиляции:
<?php
#[Printer(fields: ['id', 'name'])]
#[Arrayable(fields: ['id', 'name'])]
final class User
{
#[Constructor, Getter, With]
public int $id;
#[Constructor, Getter, Setter]
public string $name = 'guest';
}
function main(): void
{
$user = new User(1);
$user->setName('Ivan');
$copy = $user->withId(2);
echo $user->getId() . "\n"; // 1
echo $copy->getId() . "\n"; // 2
echo $user . "\n"; // User(id=1, name=Ivan)
}
Атрибут #[With] генерирует метод, который клонирует объект, меняет поле и возвращает новый экземпляр. Это удобно для иммутабельных DTO.
Прямая интеграция с C++
Если какого-то алгоритма не хватает в PHP, его можно написать на C++ и положить рядом. Связывание происходит через стаб-файл с пустым телом:
// math.cpp
#include <phpx.h>
using namespace php;
Int php_fast_sum(Int a, Int b) {
return a + b;
}
<?php
// math.stub.php
function fast_sum(int $a, int $b): int {}
<?php
// main.php
function main(): void
{
echo fast_sum(10, 20) . "\n";
}
Производительность в бенчмарках
По замерам авторов на стандартных синтетических тестах из репозитория php-src (bench.php и micro_bench.php с флагом -O3):
bench.phpпроходит за 0.603 с против 5.034 с у стандартного интерпретатора (ускорение примерно в 8 раз);micro_bench.phpвыполняется за 2.021 с против 13.045 с (ускорение в 6.5 раз).
Цифры ожидаемые для AOT-компиляции математики и ветвлений. В реальных веб-приложениях основное время уходит на ввод-вывод и работу с базой данных, поэтому там прирост будет скромнее. Зато для расчетных задач и фоновых воркеров разница ощутимая.
Ограничения и компромиссы
Перенести готовый проект на Laravel или Symfony в TypePHP пока не получится. Есть ряд строгих правил:
- Исполняемый код в глобальной области видимости запрещен, весь код должен жить внутри функций или методов.
- Для сборки бинарника требуется
main(). - Нужен PHP версии 8.4 или 8.5 со скомпилированной библиотекой
libphp.so(embed SAPI), GCC 9+ с поддержкой C++17, CMake и библиотеки GMP / MPFR для точных вычислений. - Часть динамических возможностей языка, вроде свободной смены типов или сложных ссылок, намеренно не поддерживается.
Файл конфигурации project.yml помогает управлять зависимостями и флагами оптимизации в больших проектах:
name: myapp
mode: bin
php-version: "8.5"
optimize: 2
job: 8
build-dir: build
cxx-std: c++17
sources:
- src
- cpp-src
link-libs:
- curl
Кому проект пригодится
TypePHP находится в активной разработке. У него пока меньше тысячи звезд на GitHub, но за проектом стоит опытная команда Swoole.
Пробовать проект имеет смысл, если вам нужно:
- Собрать легковесную CLI-утилиту или микросервис в один бинарный файл без необходимости ставить PHP на целевой сервер.
- Спрятать исходный код приложения при поставке клиенту on-premise, так как бинарник декомпилировать значительно сложнее байткода.
- Написать расширение для PHP с тяжелыми вычислениями, не погружаясь глубоко в Си и Zend API.
- Ускорить изолированные вычислительные модули, вроде парсеров, упаковщиков данных или алгоритмов скоринга.
Для старта достаточно клонировать репозиторий, собрать тестовый скрипт через bin/tpc.php app.php и посмотреть на генерируемый C++ в каталоге сборки.
