@ircmaxellさん作の「Recki-CT」を試してみたので紹介します。作者によるブログ記事「ircmaxell's blog: Introducing Recki-CT」によれば処理によってはHHVMやHippy VMより速いらしいのですが、この記事だけでは詳細がよくわからずモヤモヤしていました。今回ソースコードまで確認してみて、前作のPHPPHPを超える変態作品だとわかりました(参照:「PHPPHPがド変態であることに今さら気づいた」)。
Recki-CTを試してみる
Recki-CTの概要を説明すると、既存のPHP関数に型情報のアノテーションを記述して呼び出し方を少し変えることで、その関数をJITコンパイルしてネイティブコードとして実行するPHPライブラリです。JITコンパイルにはlibjitのPHPバインディングであるJIT-Fuを利用しています。
では、さっそく試してみましょう。次のコード中のgcd関数をJITコンパイルで高速化したいとします。
<?php function gcd($x, $y) { if ($x === $y) { return $x; } else if ($x < $y) { return gcd($x, $y - $x); } else { return gcd($x - $y, $y); } } $z = gcd(407074349875, 691666152);
これに対してRecki-CTを適用すると次のようなコードになります。
<?php require_once __DIR__ . '/vendor/autoload.php'; use ReckiCT\Jit; /** * @param int $x * @param int $y * @return int */ function gcd($x, $y) { if ($x === $y) { return $x; } else if ($x < $y) { return gcd($x, $y - $x); } else { return gcd($x - $y, $y); } } $gcd = Jit::JitFu('gcd'); $z = $gcd(407074349875, 691666152);
このように、十分少ない変更で既存の関数gcdをJITコンパイル対応にできます。既存の関数をJIT-Fuに書き換えるのはかなり面倒なので、これだけでも面白い成果だと言えるでしょう(参照:「PHPのlibjitバインディングであるJIT-Fuを試してみた」)。
Recki-CTの中身
上の挙動だけ見ると、Recki-CTはPHPからJIT-Fuへのトランスレータなのかな、と考えてしまいます。しかし、Recki-CTはそれよりずっと野心的なプロジェクトです。Recki-CTの処理は次のようになっています。
- PHPソースコードを受け取って構文解析して抽象構文木(AST)を作る
- 毎度おなじみ@nikicさん作のPHP-Parserを利用しています
- ASTから制御フローグラフ(CFG)を作る
- CFGを静的単一代入形式(SSA Form)にする
- 最適化を行う
- 現時点での最適化らしい処理はデッドコード削除と定数畳み込みくらい?
- 参照:lib/ReckiCT/Analyzer/OptimizerRule/
- 独自の中間表現(IR)にコンパイルする
- IRから同等のJIT-Fu関数を構築する
僕自身はコンパイラに詳しいわけではないのですが、真面目なコンパイラ実装と同様の処理を行っていると考えてよさそうです。
また、上の処理で中間表現(IR)を作るところまではJIT-Fuと全く無関係に動作します。言い方を変えると、コンパイラとしてのRecki-CTにとってJIT-Fuは対応アーキテクチャの一つでしかありません。今後JIT-Fu以外のJITコンパイラ実装が出てきても差し替えは容易ですし、PHPやその他の言語で出力することもできます。最適化フェーズで使える情報が多いため、他の方法ではできない最適化の可能性がある点も魅力ですね。