>_ DevTrendspt

Idioma

Início

Linguagens

Seções

Frontend Backend Mobile DevOps AI / ML GameDev Blockchain Embarcados Segurança
PHP

Compilar PHP em Binário Nativo Sem Interpretador

Há cerca de quinze anos, o Facebook tentou resolver os problemas de performance do seu monólito com o compilador HPHPc, que convertia PHP para C++. Mais tarde, abandonaram essa abordagem em favor da máquina virtual HHVM, e o PHP 8 introduziu um JIT integrado. Parecia que a ideia de um compilador puramente Ahead-Of-Time para PHP havia ficado na história para sempre. A equipe do Swoole decidiu revisitar essa ideia e lançou o projeto TypePHP.

Este é um compilador AOT completo escrito em PHP. Ele traduz código fonte PHP 8.4+ em C++17, depois compila via GCC ou Clang em código de máquina nativo. Sem opcodes em tempo de execução, sem warmup de JIT, e sem interpretador no runtime.

O compilador é totalmente self-hosting. O utilitário tpc constrói a si mesmo a partir de fontes PHP, sem qualquer código C glue dentro do próprio compilador.

Como o TypePHP Funciona

A compilação acontece em dois estágios. Primeiro, o analisador parseia o código do projeto, coleta metadados sobre classes e funções, e constrói uma tabela de símbolos completa. No segundo estágio, os corpos das funções são traduzidos para C++17. Em hot paths, o compilador gera C++ estático rápido, enquanto para construções dinâmicas como reflection ou chamadas de funções do sistema, ele se conecta ao runtime Zend através da camada PHPX.

A saída pode ser um de três formatos:

  • Binário executável (bin). Um arquivo independente para utilitários de console e daemons. Requer um ponto de entrada main().
  • Extensão PHP (ext). Um módulo regular .so ou .dll que pode ser carregado no php.ini padrão.
  • Biblioteca dinâmica (lib). Um arquivo binário com arquivos .stub.php gerados para chamada de outros projetos.
  • Componente WASI para execução em ambiente WebAssembly.

O Que Muda na Sintaxe

O TypePHP não tenta suportar todos os truques dinâmicos do PHP padrão. Os criadores apostam em tipagem estrita onde máxima velocidade é necessária.

Tipos Escalares Nativos

A diretiva use native_types instrui o compilador a mapear int, float e bool diretamente para tipos C++ (int64_t, double, bool). Uma variável com tal tipo não pode mais mudar de tipo subitamente no meio da execução de uma função. Mas o processador executa instruções de máquina puras sem desempacotar 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";
}

É construído com um único comando:

bin/tpc.php fib.php -O3 -o fib
./fib 35

Contêineres com Tipagem Estrita

Arrays regulares do PHP são universais mas famintos de memória e lentos devido a tabelas hash. O TypePHP adiciona estruturas std::vector, std::array, std::map e 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;
}

Nos testes dos desenvolvedores, um loop atualizando elementos em std::array levou 6.4 segundos versus 67.6 segundos para um array PHP regular com JIT habilitado. A velocidade essencialmente igualou um vector C++ escrito à mão (6.2 segundos).

Métodos em Primitivos

Em vez de um monte de funções como strlen(), strtoupper() ou in_array(), você pode chamar métodos diretamente em tipos base. O compilador resolve essas chamadas em tempo de build e as converte para chamadas diretas de funções C sem overhead de tabela virtual:

<?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));
}

Geração de Código Template via Atributos

Para evitar escrever dezenas de getters e setters manualmente, o TypePHP processa atributos customizados durante a compilação:

<?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)
}

O atributo #[With] gera um método que clona o objeto, altera o campo e retorna uma nova instância. Isso é conveniente para DTOs imutáveis.

Integração Direta com C++

Se algum algoritmo está faltando no PHP, você pode escrevê-lo em C++ e colocá-lo por perto. O linking acontece via um stub file com um corpo vazio:

// 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";
}

Performance nos Benchmarks

De acordo com as medições dos autores em testes sintéticos padrão do repositório php-src (bench.php e micro_bench.php com a flag -O3):

  • bench.php completa em 0.603 s versus 5.034 s para o interpretador padrão (aproximadamente 8x de melhoria de velocidade);
  • micro_bench.php executa em 2.021 s versus 13.045 s (6.5x de melhoria de velocidade).

Os números são esperados para compilação AOT de matemática e branching. Em aplicações web reais, a maior parte do tempo vai para operações de I/O e banco de dados, então a melhoria será mais modesta lá. Mas para tarefas computacionais e workers em background, a diferença é perceptível.

Limitações e Trade-offs

Você ainda não pode portar um projeto Laravel ou Symfony existente para o TypePHP. Há uma série de regras estritas:

  • Código executável em escopo global é proibido — todo código deve viver dentro de funções ou métodos.
  • PHP 8.4 ou 8.5 com a biblioteca libphp.so compilada (embed SAPI) é necessário para construir um binário.
  • Você precisa de GCC 9+ com suporte a C++17, CMake, e bibliotecas GMP/MPFR para cálculos precisos.
  • Algumas features dinâmicas da linguagem, como troca livre de tipos ou referências complexas, não são suportadas intencionalmente.

O arquivo de configuração project.yml ajuda a gerenciar dependências e flags de otimização em projetos grandes:

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

Quem Se Beneficiará Deste Projeto

O TypePHP está em desenvolvimento ativo. Tem menos de mil estrelas no GitHub até agora, mas o projeto é apoiado pela experiente equipe do Swoole.

Faz sentido experimentar o projeto se você precisa:

  • Construir um utilitário CLI leve ou microsserviço como um binário único sem precisar instalar PHP no servidor alvo.
  • Ocultar código fonte da aplicação ao entregar para um cliente on-premise, já que binários são significativamente mais difíceis de descompilar que bytecode.
  • Escrever uma extensão PHP com computações pesadas sem mergulhar profundamente em C e API Zend.
  • Acelerar módulos computacionais isolados, como parsers, empacotadores de dados, ou algoritmos de pontuação.

Para começar, basta clonar o repositório, construir um script de teste via bin/tpc.php app.php, e olhar o C++ gerado no diretório build.

Projetos relacionados