PHPをインタープリタなしでネイティブバイナリにコンパイルする
約15年前、FacebookはHPHPcコンパイラ(PHPをC++に変換)でモノリスのパフォーマンス問題を解決しようとしました。その後、このアプローチをHHVM仮想マシンに置き換え、PHP 8では組み込みJITが導入されました。純粋なAOT(Ahead-Of-Time)コンパイラというアイデアは完全に歴史に埋もれたかに見えましたが、Swooleチームが再びこれに着手し、TypePHPプロジェクトをリリースしました。
これはPHP自身で書かれた本格的なAOTコンパイラです。PHP 8.4+のソースコードをC++17に変換し、GCCまたはClangでネイティブマシンコードにコンパイルします。ランタイムにオペコードはなく、JITウォームアップもなく、ランタイムにインタープリタもありません。
コンパイラは完全にセルフホスティングです。ユーティリティはPHPソースから自分自身をビルドし、コンパイラ自体にはCのグルーコードがありません。
TypePHPの動作原理
コンパイルは2段階で発生します。まず、アナライザがプロジェクトコードを解析し、クラスと関数に関するメタデータを収集して、完全なシンボルテーブルを構築します。2番目の段階で、関数本体がC++17に変換されます。ホットパスでは、コンパイラは高速な静的C++を生成し、リフレクションやシステム関数呼び出しなどの動的なコンストラクトに対しては、PHPXレイヤーを通じてZendランタイムに接続します。
出力は3つの形式のいずれかになります:
- 実行可能バイナリ(
bin)。コンソールユーティリティやデーモン向けのスタンドアロンファイル。エントリポイントとしてmain()が必要です。 - PHPエクステンション(
ext)。標準のphp.iniにロードできる通常の.soまたは.dllモジュール。 - 動的ライブラリ(
lib)。他のプロジェクトから呼び出すためのバイナリファイルと、生成されたファイル。 - WebAssembly環境で実行するためのWASIコンポーネント。
構文の変更点
TypePHPは標準PHPのすべての動的テクニックをサポートしようとはしていません。作成者は、最大速度が必要な場面で厳密な型付けに賭けています。
ネイティブスカラ型
директиваは、int、float、boolをC++型(int64_t、double、bool)に直接マッピングするようコンパイラに指示します。この型を持つ変数は、関数の実行途中で突然型を変更できなくなります。しかし、プロセッサはの展開なしで純粋なマシン命令を実行します。
単一のコマンドでビルドできます:
bin/tpc.php fib.php -O3 -o fib
./fib 35
厳密に型付けされたコンテナ
通常のPHP配列は普遍的ですが、ハッシュテーブル 때문에メモリ集約的で低速です。TypePHPは構造体を追加しています。
開発者のテストでは、での要素を更新するループに6.4秒かかり、JIT有効な通常のPHP配列では67.6秒かかりました。速度は本質的に手書きのC++ vector(6.2秒)と一致しました。
<?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;
}
プリミティブ型のメソッド
やなどの一堆の関数代わりに、基底型で直接メソッドを呼び出すことができます。コンパイラはこのような呼び出しをビルド時に解決し、仮想テーブルオーバーヘッドなしで直接C関数呼び出しに変換します:
<?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));
}
属性によるテンプレートコード生成
何十ものgetterとsetterを手書きすることを避けるため、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)
}
属性は、オブジェクトをクローンし、フィールドを変更して、新しいインスタンスを返すメソッドを生成します。これは不変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";
}
ベンチマークでのパフォーマンス
リポジトリからの標準的な合成テスト(フラグ付きおよび)での作者の測定値によると:
- は標準インタープリタの5.034秒に対して0.603秒で完了します(約8倍の高速化);
- は13.045秒に対して2.021秒で実行されます(6.5倍の高速化)。
数値は数学と分岐のAOTコンパイル_expectedです。実際のWebアプリケーションでは、時間の大部分はI/Oとデータベース操作にかかるため、改善はそこでより控えめになります。しかし、計算タスクやバックグラウンドワーカーでは違いが顕著です。
制限事項とトレードオフ
既存のLaravelまたはSymfonyプロジェクトをTypePHPに移植することはできません。まだ多くの厳格なルールがあります:
- グローバルスコープでの実行可能コードは禁止—all code must live inside functions or methods.
- バイナリをビルドするには、PHP 8.4または8.5とコンパイル済みライブラリ(embed SAPI)が必要です。
- GCC 9+(C++17サポート)、CMake、精密計算用のGMP/MPFRライブラリが必要です。
- 自由型切り替えや複雑な参照など、一部の動的言語機能は意図的にサポートされていません。
設定ファイルは、大規模プロジェクトでの依存関係と最適化フラグの管理に役立ちます:
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でのスターはまだ1000未満ですが、経験豊富なSwooleチームがプロジェクトをバックアップしています。
次が必要な場合にプロジェクトを試す意味があります:
- ターゲットのサーバーにPHPをインストールする必要なく、ライトウェイトなCLIユーティリティまたはマイクロサービスを単一バイナリとしてビルドする。
- クライアントにオンプレミスで納品する際にアプリケーションのソースコードを隠す—バイナリはバイトコードより大幅に逆コンパイルが難しいです。
- CやZend APIに深く潜ることなく、重い計算処理を持つPHPエクステンションを書く。
- パーサー、データパッカー、スコアリングアルゴリズムなど、孤立した計算モジュールを高速化する。
始めるには、リポジトリをクローンし、でテストスクリプトをビルドし、buildディレクトリ内の生成されたC++を確認してください。
関連プロジェクト