
# ベンチマーク

Phasisは、ディスパッチ負荷の高いJavaScriptでV8より**およそ2桁遅くなります**。この限界は、ネイティブJITコンパイラーに対するPHPホスト型インタープリターに本質的に伴うコストです。組み込み側が考えるべきなのは「V8と比べてどうか」ではなく、「自分の用途に十分な速さか」です。多くの組み込み用途では、答えは「はい」です。

## この限界が問題にならない場合

- **テンプレート処理** — 一般的なページのSSR形式のレンダリングは10 msを大幅に下回ります。
- **検証ルール** — ユーザーが指定したJSによるフォーム検証はマイクロ秒で完了します。
- **データ変換** — 数百件に対する`array.map(transform)`は数十ミリ秒で完了します。
- **サンドボックス実行** — 実行時間をホスト側で1秒に制限した短いユーザースクリプトなら、十分な余裕があります。

## この限界が問題になる場合

- **計算負荷の高い内側ループ** — 画像処理、大きな配列の縮約、シミュレーションなどです。計算処理をネイティブのPHPライブラリーに移し、[ホスト関数](https://phasis.inline0.dev/ja/docs/interop/host-functions)として公開してください。
- **リアルタイムアニメーション** — 60 fpsで動作するものはすべて対象です。そのJSは組み込みエンジンではなく、クライアント（ブラウザー、ネイティブアプリ）で実行してください。
- **計算負荷の高いアルゴリズムのJSによる再実装** — 暗号化、圧縮、解析などです。PHPには1000×高速なネイティブ版があるため、そちらを使ってください。

適切な捉え方は、PhasisをPHP形式のアプリケーションに埋め込む、JS形式のロジックをつなぐ**糊付け層**と考えることです。計算処理はPHPホストが担い、JS層は設定、分岐、小規模な変換を担います。

## 素朴なツリーウォーカーと比べてエンジンが高速な理由

- **バイトコードVM** — ホットなディスパッチの大半はASTではなく、`VM::execute`のopcodeスイッチを通ります。
- **カスタム呼び出しスタック** — すべてのJS呼び出しの状態を`VM::execute`内に保持し、呼び出し元のフレームをプールに保存します。JS呼び出しごとに新しいPHPフレームを作りません。詳細は[バイトコードVM § カスタム呼び出しスタック](https://phasis.inline0.dev/ja/docs/advanced/bytecode-vm)を参照してください。
- **インラインキャッシュ** — PCごとの単相キャッシュにより、プロトタイプメソッドをインスタンスから読み取る際（`c.method()`の形）、プロトタイプチェーンの走査を省略できます。
- **フレームスナップショットジェネレーター** — 単純なジェネレーター本体は、PHP Fiberを経由せず、ディスパッチ状態をヒープオブジェクトに保存して中断します。
- **JsToPhpトランスパイラー** — 数値処理やローカル変数中心の本体（`obj.method()`、`new Ctor()`、`this.member`の読み取りを含む）はPHPクロージャーにコンパイルされ、PHP 8.5のトレーシングJITによってネイティブマシンコードに低レベル化されます。
- **JSON.stringifyキャッシュ** — `JSON.stringify(o)`は推移的なバージョン無効化を行い、入力オブジェクトに出力をキャッシュするため、`JSON.parse(JSON.stringify(o))`というディープクローンの慣用表現を高速化します。

## 独自のワークロードを測定する

```bash
php bench/run.php
```

これは`bench/microbench.js`に含まれるマイクロベンチマークスイートを実行します。ループ演算、再帰関数、オブジェクト生成、プロトタイプメソッド呼び出し、配列への追加、JSONの往復変換など、いくつかの合成ワークロードを測定し、各テストの実行時間の中央値を出力します。動作確認に便利です。現在の数値は、各CIベンチマーク実行時に[`BENCH.md`](https://github.com/inline0/phasis/blob/main/BENCH.md)へコミットされます。

独自のコードを測定するには、任意のスクリプトに置き換えてください。

```bash
php -d xdebug.mode=off bench/run.php path/to/your-script.js
```

PHPレベルのプロファイリングには、XdebugのプロファイラーをPhasisに対して、他のPHPアプリケーションと同じように使えます。`xdebug.mode=profile`でスクリプトを実行し、cachegrindの出力を調べて、どの`Phasis\`クラスが処理時間の大半を占めるか確認してください。

## ロードマップ

実現の容易さをおおまかな順に並べると、V8との差をさらに縮めるには次の対応が考えられます。

- **多相インラインキャッシュ** — 1つの`LOAD_MEMBER`呼び出し箇所で、低速なディスパッチに切り替わる前に2〜4個の形状をキャッシュできるようにします。異なるレシーバーを処理するコードに効果があります。
- **`yield*`と、フレームスナップショットジェネレーター経路でのtry-with-yield** — 現状では、これらの形はFiberベースのツリーウォーカーに送られます。スナップショットに対応させるには、明示的な状態機械での処理（委譲イテレーターの転送、中断中もハンドラースタックを保持）がそれぞれ必要です。
- **非同期ジェネレーターとスナップショット上の`await`** — 仕様では、ジェネレーターの中断とマイクロタスクキューが組み合わさります。バイトコード本体が`yield`と`await`の両方に対応すれば、非同期ジェネレーターもスナップショットに対応できます。
- **JsToPhpでのパラメーター型推論** — 関数パラメーターは既定で数値型となるため、`function (obj) { obj.method() }`ではコンパイルが中断されます。本体内での使われ方からオブジェクト型のパラメーターを推論できれば、`new`でレシーバーをローカルに生成しなくても、関数引数のメソッドを呼び出せるようになります。
- **opcacheへの直接出力** — opcache形式のopcodeを直接書き込み、JsToPhpの`eval()`段階を省略します。最も大がかりな項目ですが、ほかの対応は既存のアーキテクチャ内で実現できます。

これらはどれも、Phasisが対象とする組み込み用途には必要ありません。
