>_ DevTrendsit

Lingua

Home

Linguaggi

Sezioni

Frontend Backend Mobile DevOps AI / ML GameDev Blockchain Embedded Sicurezza
PHP

Compilare PHP in un Binario Nativo Senza Interprete

Circa quindici anni fa, Facebook tentò di risolvere i problemi di prestazioni del loro monolite con il compilatore HPHPc, che convertiva PHP in C++. Successivamente, abbandonarono questo approccio a favore della macchina virtuale HHVM, e PHP 8 introdusse un JIT integrato. Sembrava che l'idea di un puro compilatore Ahead-Of-Time per PHP fosse scomparsa dalla storia per sempre. Il team Swoole ha deciso di riprenderla e ha rilasciato il progetto TypePHP.

Questo è un compilatore AOT completo scritto in PHP stesso. Traduce il codice sorgente PHP 8.4+ in C++17, poi lo compila tramite GCC o Clang in codice macchina nativo. Nessun opcode a runtime, nessun warmup JIT, e nessun interprete nel runtime.

Il compilatore è completamente self-hosting. L'utility tpc si compila da sorgenti PHP, senza alcun codice C di collegamento all'interno del compilatore stesso.

Come Funziona TypePHP

La compilazione avviene in due fasi. Prima, l'analizzatore analizza il codice del progetto, raccoglie metadati su classi e funzioni, e costruisce una tabella completa dei simboli. Nella seconda fase, i corpi delle funzioni vengono tradotti in C++17. Nei percorsi critici (hot paths), il compilatore genera C++ statico veloce, mentre per costrutti dinamici come la riflessione o le chiamate a funzioni di sistema, si connette al runtime Zend attraverso il layer PHPX.

L'output può essere in uno di tre formati:

  • Binario eseguibile (bin). Un file autonomo per utility console e daemon. Richiede un punto di ingresso main().
  • Estensione PHP (ext). Un normale modulo .so o .dll che può essere caricato in php.ini standard.
  • Libreria dinamica (lib). Un file binario con file .stub.php generati per essere chiamato da altri progetti.
  • Componente WASI per l'esecuzione in un ambiente WebAssembly.

Cosa Cambia nella Sintassi

TypePHP non cerca di supportare tutti i trucchi dinamici del PHP standard. I creatori puntano sulla tipizzazione rigorosa dove è necessaria la massima velocità.

Tipi Scalari Nativi

La direttiva use native_types indica al compilatore di mappare int, float e bool direttamente ai tipi C++ (int64_t, double, bool). Una variabile con un tale tipo non può più cambiare improvvisamente tipo nel mezzo dell'esecuzione di una funzione. Ma il processore esegue istruzioni macchina pure senza spacchettare 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";
}

Si costruisce con un singolo comando:

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

Contenitori Strictly Typed

Gli array PHP regular sono universali ma affamati di memoria e lenti a causa delle tabelle hash. TypePHP aggiunge le strutture 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;
}

Nei test degli sviluppatori, un loop che aggiornava elementi in std::array ha impiegato 6,4 secondi contro 67,6 secondi per un array PHP regular con JIT abilitato. La velocità era essenzialmente uguale a un vettore C++ scritto a mano (6,2 secondi).

Metodi sui Primitivi

Invece di una serie di funzioni come strlen(), strtoupper() o in_array(), puoi chiamare metodi direttamente sui tipi base. Il compilatore risolve queste chiamate a tempo di build e le converte in chiamate a funzioni C dirette senza overhead di tabella virtuale:

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

Generazione di Codice Template tramite Attributi

Per evitare di scrivere decine di getter e setter a mano, TypePHP processa attributi personalizzati durante la compilazione:

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

L'attributo #[With] genera un metodo che clona l'oggetto, modifica il campo e restituisce una nuova istanza. È comodo per DTO immutabili.

Integrazione Diretta con C++

Se manca qualche algoritmo in PHP, puoi scriverlo in C++ e posizionarlo vicino. Il linking avviene tramite un file stub con un corpo vuoto:

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

Prestazioni nei Benchmark

Secondo le misurazioni degli autori su test sintetici standard dal repository php-src (bench.php e micro_bench.php con il flag -O3):

  • bench.php completa in 0,603 s contro 5,034 s dell'interprete standard (circa 8x di speedup);
  • micro_bench.php esegue in 2,021 s contro 13,045 s (6,5x di speedup).

I numeri sono attesi per la compilazione AOT di matematica e branching. Nelle applicazioni web reali, la maggior parte del tempo va alle operazioni di I/O e database, quindi il miglioramento sarà più modesto lì. Ma per task computazionali e worker in background, la differenza è evidente.

Limitazioni e Compromessi

Non puoi ancora portare un progetto Laravel o Symfony esistente su TypePHP. Ci sono una serie di regole rigide:

  • Il codice eseguibile nello scope globale è proibito—tutto il codice deve risiedere dentro funzioni o metodi.
  • È richiesto PHP 8.4 o 8.5 con la libreria libphp.so compilata (embed SAPI) per costruire un binario.
  • Hai bisogno di GCC 9+ con supporto C++17, CMake e le librerie GMP/MPFR per calcoli precisi.
  • Alcune funzionalità dinamiche del linguaggio, come il cambio di tipo libero o riferimenti complessi, sono intenzionalmente non supportate.

Il file di configurazione project.yml aiuta a gestire dipendenze e flag di ottimizzazione in progetti grandi:

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

Chi Beneficerà da Questo Progetto

TypePHP è in fase di sviluppo attivo. Ha meno di mille stelle su GitHub finora, ma il progetto è supportato dal team Swoole esperto.

Ha senso provare il progetto se hai bisogno di:

  • Costruire un'utility CLI leggera o un microservizio come singolo binario senza bisogno di installare PHP sul server target.
  • Nascondere il codice sorgente dell'applicazione quando si consegna a un cliente on-premise, dato che i binari sono significativamente più difficili da decompilare rispetto al bytecode.
  • Scrivere un'estensione PHP con calcoli pesanti senza immergersi profondamente in C e Zend API.
  • Velocizzare moduli computazionali isolati, come parser, packer di dati o algoritmi di scoring.

Per iniziare, basta clonare il repository, costruire uno script di test tramite bin/tpc.php app.php, e guardare il C++ generato nella directory build.

Progetti correlati