>_ DevTrendsde

Sprache

Start

Sprachen

Bereiche

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

PHP in eine native Binärdatei ohne Interpreter kompilieren

Vor etwa fünfzehn Jahren versuchte Facebook, die Performance-Probleme ihres Monolithen mit dem HPHPc-Compiler zu lösen, der PHP in C++ umwandelte. Später gaben sie diesen Ansatz zugunsten der HHVM-Virtual Machine auf, und PHP 8 führte einen eingebauten JIT ein. Es schien, als wäre die Idee eines reinen Ahead-Of-Time-Compilers für PHP endgültig der Geschichte anheimgefallen. Das Swoole-Team beschloss, diesen Ansatz wieder aufzugreifen, und veröffentlichte das TypePHP-Projekt.

Dies ist ein vollwertiger AOT-Compiler, der in PHP selbst geschrieben ist. Er übersetzt PHP 8.4+-Quellcode nach C++17 und kompiliert ihn dann über GCC oder Clang in nativen Maschinencode. Keine Opcodes zur Laufzeit, kein JIT-Warmup und kein Interpreter in der Runtime.

Der Compiler ist vollständig selbst-hostend. Das tpc-Utility baut sich aus PHP-Quellen auf, ohne jeglichen C-Glue-Code innerhalb des Compilers selbst.

So funktioniert TypePHP

Die Kompilierung erfolgt in zwei Phasen. Zunächst parst der Analyzer den Projektcode, sammelt Metadaten über Klassen und Funktionen und baut eine vollständige Symboltabelle auf. In der zweiten Phase werden Funktionskörper nach C++17 übersetzt. In Hot Paths generiert der Compiler schnellen statischen C++-Code, während für dynamische Konstrukte wie Reflection oder Systemfunktionsaufrufe eine Verbindung zur Zend-Runtime über die PHPX-Schicht hergestellt wird.

Die Ausgabe kann eines von drei Formaten sein:

  • Ausführbare Binärdatei (bin). Eine eigenständige Datei für Konsolen-Utilities und Daemons. Sie erfordert einen Einstiegspunkt main().
  • PHP-Extension (ext). Ein reguläres .so- oder .dll-Modul, das in standard php.ini geladen werden kann.
  • Dynamische Bibliothek (lib). Eine Binärdatei mit generierten .stub.php-Dateien zum Aufrufen aus anderen Projekten.
  • WASI-Komponente zum Ausführen in einer WebAssembly-Umgebung.

Was sich in der Syntax ändert

TypePHP versucht nicht, alle dynamischen Tricks von Standard-PHP zu unterstützen. Die Entwickler setzen auf strikte Typisierung, wo maximale Geschwindigkeit benötigt wird.

Native skalare Typen

Die use native_types-Direktive weist den Compiler an, int, float und bool direkt auf C++-Typen abzubilden (int64_t, double, bool). Eine Variable mit einem solchen Typ kann nicht mehr plötzlich mitten in der Funktionsausführung ihren Typ ändern. Aber der Prozessor führt reine Maschinenanweisungen ohne Entpacken von zval aus.

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

Es wird mit einem einzigen Befehl gebaut:

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

Strikt typisierte Container

Reguläre PHP-Arrays sind universell, aber speicherintensiv und langsam aufgrund von Hash-Tabellen. TypePHP fügt std::vector, std::array, std::map und std::ordered_map-Strukturen hinzu.

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

In den Tests der Entwickler dauerte ein Loop, der Elemente in std::array aktualisiert, 6,4 Sekunden gegenüber 67,6 Sekunden für ein reguläres PHP-Array mit aktiviertem JIT. Die Geschwindigkeit entsprach im Wesentlichen handgeschriebenem C++-Vector (6,2 Sekunden).

Methoden auf Primitiven

Statt einer Reihe von Funktionen wie strlen(), strtoupper() oder in_array() können Sie Methoden direkt auf Basistypen aufrufen. Der Compiler löst solche Aufrufe zur Build-Zeit auf und konvertiert sie in direkte C-Funktionsaufrufe ohne virtuellen Tabellensoverhead:

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

Generierung von Template-Code über Attribute

Um Dutzende von Gettern und Settern nicht von Hand schreiben zu müssen, verarbeitet TypePHP benutzerdefinierte Attribute während der Kompilierung:

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

Das #[With]-Attribut generiert eine Methode, die das Objekt klont, das Feld ändert und eine neue Instanz zurückgibt. Dies ist praktisch für immutable DTOs.

Direkte C++-Integration

Wenn ein bestimmter Algorithmus in PHP fehlt, können Sie ihn in C++ schreiben und in der Nähe ablegen. Das Linking erfolgt über eine Stub-Datei mit einem leeren Body:

// 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 in Benchmarks

Den Messungen der Autoren auf Standard-Synthetests aus dem php-src-Repository (bench.php und micro_bench.php mit dem -O3-Flag) zufolge:

  • bench.php wird in 0,603 s abgeschlossen gegenüber 5,034 s für den Standard-Interpreter (ca. 8x Speedup);
  • micro_bench.php wird in 2,021 s ausgeführt gegenüber 13,045 s (6,5x Speedup).

Die Zahlen entsprechen den Erwartungen für AOT-Kompilierung von Mathe- und Verzweigungsoperationen. In realen Web-Anwendungen geht die meiste Zeit für I/O und Datenbankoperationen drauf, sodass die Verbesserung dort bescheidener ausfallen wird. Aber für rechenintensive Aufgaben und Background-Worker ist der Unterschied deutlich spürbar.

Einschränkungen und Kompromisse

Ein bestehendes Laravel- oder Symfony-Projekt kann noch nicht auf TypePHP portiert werden. Es gibt eine Reihe strenger Regeln:

  • Ausführbarer Code im globalen Scope ist verboten – aller Code muss innerhalb von Funktionen oder Methoden leben.
  • PHP 8.4 oder 8.5 mit der kompilierten libphp.so-Bibliothek (embed SAPI) wird benötigt, um eine Binärdatei zu bauen.
  • Sie benötigen GCC 9+ mit C++17-Support, CMake und GMP/MPFR-Bibliotheken für präzise Berechnungen.
  • Einige dynamische Sprachfeatures wie freies Type-Switching oder komplexe Referenzen werden absichtlich nicht unterstützt.

Die project.yml-Konfigurationsdatei hilft bei der Verwaltung von Abhängigkeiten und Optimierungs-Flags in großen Projekten:

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

Wer wird von diesem Projekt profitieren

TypePHP befindet sich in aktiver Entwicklung. Es hat weniger als tausend Stars auf GitHub, aber das Projekt wird vom erfahrenen Swoole-Team unterstützt.

Es macht Sinn, das Projekt auszuprobieren, wenn Sie:

  • Ein leichtgewichtiges CLI-Utility oder einen Microservice als einzelne Binärdatei bauen möchten, ohne PHP auf dem Zielserver installieren zu müssen.
  • Den Anwendungssourcecode verbergen möchten, wenn Sie an einen On-Premise-Kunden liefern, da Binärdateien deutlich schwerer zu dekompilieren sind als Bytecode.
  • Eine PHP-Extension mit schweren Berechnungen schreiben möchten, ohne tief in C und Zend API einzutauchen.
  • Isolierte rechenintensive Module beschleunigen möchten, wie Parser, Datenpacker oder Scoring-Algorithmen.

Um zu starten, klonen Sie einfach das Repository, bauen Sie ein Testskript über bin/tpc.php app.php und schauen Sie sich den generierten C++-Code im Build-Verzeichnis an.

Ähnliche Projekte