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 ingressomain(). - Estensione PHP (
ext). Un normale modulo.soo.dllche può essere caricato inphp.inistandard. - Libreria dinamica (
lib). Un file binario con file.stub.phpgenerati 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.phpcompleta in 0,603 s contro 5,034 s dell'interprete standard (circa 8x di speedup);micro_bench.phpesegue 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.socompilata (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