Скомпилировать PHP в нативный бинарник без интерпретатора

29 авг 2026
799
38
10
1 неделя

Лет пятнадцать назад 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++ в каталоге сборки.

🍪 Мы используем файлы cookie и сервис аналитики Яндекс.Метрика, чтобы сайт работал лучше. Продолжая пользоваться devtrends.ru, вы соглашаетесь с обработкой данных согласно Политике конфиденциальности.