こんにちは。PHPの裏側を覗く旅へようこそ。
普段、私たちは何気なくLaravelやSymfonyといったモダンなフレームワークを使い、美しいコントローラーを書いていますよね。しかし、1リクエストのライフサイクルが終わり、FPM(FastCGI Process Manager)のプロセスがレスポンスを返した瞬間、そこで動いていたメモリやコンテキストは綺麗に消え去ります。
「PHPはリブートのたびにすべてを捨てる言語だ」——長年、そう言われてきました。
しかし、PHP 8.1で導入された Fiber(ファイバー) は、その常識を静かに、しかし根本から覆しつつあります。Node.jsのasync/awaitやGoのgoroutineに魅せられたエンジニアなら、「PHPで真の協調的マルチタスク(Cooperative Multitasking)はどう動いているのか」気になったことがあるはずです。
今回は、よく混同されがちな Generator(ジェネレータ) と Fiber(ファイバー) の決定的な違いについて、PHPエンジンの内部挙動やメモリ管理の観点から深く掘り下げてみましょう。「ここを理解すれば、PHPの裏側が綺麗に見えますよ」と、優しく紐解いていきますね。
—
1. そもそもGeneratorとFiberは何が違うのか?
まず、大前提としての「概念の整理」から入りましょう。どちらも処理を途中で一時停止(Suspend)し、後から再開(Resume)できるという点で似ています。しかし、Zendエンジンにおける「コールスタックの所有権」が全く異なります。
- Generator(PHP 5.5〜):
- 本質: イテレータの糖衣構文。
- スタック: 独自のローカル変数を保持しますが、関数呼び出しの「深さ(コールスタック)」をまたぐサスペンドはできません。あくまでのっぺらぼうな1つの関数の内部で `yield` するだけです。
- Fiber(PHP 8.1〜):
- 本質: 独立したファイバー(軽量スレッド)コンテキスト。
- スタック: 関数Aから関数Bを呼び出し、そのさらに奥深くの関数Cの内部からであっても、一気に親のコンテキストへ処理を巻き戻す(サスペンドする)ことができます。
図解するまでもなく、Generatorが「平屋の部屋の出入り」だとすれば、Fiberは「何階層もあるビルディングのエレベーターを途中で止めて、別の階にジャンプする」ようなものです。
—
2. 内部構造(Zend VM)から見るコストの正体
では、PHPのランタイム(Zend VM)の裏側では何が起きているのでしょうか?
PHPのスクリプトは、パースされて「オペコード(Opcode)」に変換されます。通常の関数呼び出しが行われると、Zend VMはC言語のコールスタック上に `_zend_execute_data` という実行コンテキストを積み上げていきます。
Generatorのメモリフットプリント
Generatorが生成されるとき、Zend VMは通常の関数フレームを少し拡張した `zend_generator` オブジェクトをヒープ上にアロケートします。
ローカル変数や実行位置(Oplineポインタ)はそこに保存されますが、コールスタックそのものは巻き戻されません。そのため、メモリ消費量は極めて少なく、オーバーヘッドも無視できるほど軽量です。
Fiberのメモリフットプリント
一方、Fiberは独立したCスタックの領域(あるいはそれに準ずるZend VMの実行スタックチェーン)を構築します。
`Fiber::suspend()` が呼ばれた瞬間、Zend VMは現在の `execute_data` のチェーンを退避させ、制御権を呼び出し元(メインの実行コンテキスト)へと返します。
つまり、Fiberは「スタックを持つマイクロスレッド」なのです。これにより、非同期I/O待ちの間に他の処理にコンテキストを切り替える(協調的マルチタスク)ことが可能になりますが、Generatorに比べるとインスタンス生成時のメモリコストやコンテキストスイッチのオーバーヘッドが若干高くなります。
—
3. 実践:Generatorベースの「疑似」非同期と、Fiberの本物の非同期
言葉だけではピンとこないと思いますので、コードを見てみましょう。
かつてのPHPで非同期処理(あるいはコルーチン風の挙動)を実現しようとすると、Generatorを使った「イテレータのネスト」を書く必要がありました。
パターンA: Generatorによる協調的タスク(古き良き手法)
$gen) {
if ($gen->valid()) {
$gen->next(); // 1ステップ進める
} else {
unset($generators[$index]);
}
}
}
このコード、動くには動きますが、大きな欠点があります。もし `task_a()` の中でさらに深い関数(例: `api_client_call()`)を呼び出し、その奥で待機したくなったらどうでしょう?
呼び出し元の関数すべてに `yield` を伝搬させなければならず、いわゆる “What color is your function?”(関数の色問題) に直面します。既存のサードパーティライブラリをそのまま非同期化するのは不可能でした。
—
パターンB: Fiberによる真のコールスタック脱出
Fiberの真骨頂は、「どこからでも」サスペンドできる点にあります。深いネストの関数呼び出しの途中であっても、一気に外側へ脱出できます。
データベースへクエリ送信…\n”;
// どこからでもサスペンドできる!
// ここでメインのイベントループに制御が戻ります
$result = Fiber::suspend(“DB_RESULT_DATA”);
echo ” -> クエリ結果を受け取りました: {$result}\n”;
}
function business_logic() {
echo “ビジネスロジック実行中…\n”;
deep_database_query(); // さらに深い階層からサスペンド
}
// Fiberのインスタンス化
$fiber = new Fiber(function () {
business_logic();
});
// Fiberを開始(最初の suspend まで実行される)
echo “1. ファイバー起動\n”;
$valueFromFiber = $fiber->start();
echo “メイン側: ファイバーから値を受け取りました -> ‘{$valueFromFiber}’\n”;
// 外部で何らかの非同期処理(I/Oなど)が終わったと仮定して、
// 再びFiberに値を渡して再開する
echo “2. ファイバー再開\n”;
$fiber->resume(“【超特急のレスポンス】”);
// 実行結果:
// 1. ファイバー起動
// ビジネスロジック実行中…
// -> データベースへクエリ送信…
// メイン側: ファイバーから値を受け取りました -> ‘DB_RESULT_DATA’
// 2. ファイバー再開
// -> クエリ結果を受け取りました: 【超特急のレスポンス】
このコードを実行すると、PHPの裏側でコンテキストが美しく保存・復元されているのが体感できるはずです。`business_logic()` や `deep_database_query()` のシグネチャを変更することなく、あたかも同期処理を書いているかのような自然な記述で、非同期的な割り込み制御が実現できています。
—
4. パフォーマンス特性とユースケースの境界線
「じゃあ、これからは全部Fiberで作ればいいの?」という疑問がわきますよね。
ここがアーキテクトとしての腕の見せ所です。何でもかんでもFiberを使うべきではありません。それぞれの境界線(トレードオフ)をしっかりと見極めましょう。
Generatorを使うべきユースケース
- 巨大なデータセットのストリーミング処理: 数百万行のCSVファイルやデータベースの全件フェッチなど、メモリを枯渇させずに順次処理したい場合。
- 遅延評価(Lazy Evaluation): 無限シーケンスや、計算コストの高い配列のオンデマンド生成。
- 理由: Fiberほどの複雑なコンテキストスイッチのオーバーヘッドがなく、純粋にイテレータとしての高速性と省メモリ性を享受できます。
Fiberを使うべきユースケース
- イベントループを軸にした非同期HTTPクライアントやWebsocketサーバー: ReactPHPやAmp(v3以降)などのエコシステムを活用し、外部APIへのリクエストやDB接続をノンブロックで並行実行したい場合。
- 「同期的な書き心地」を維持した非同期フレームワークの構築: コールバック地獄(Promiseのチェイン)を避け、直感的なコードで高いスループットを実現したい場合。
- 理由: コードの可読性を保ったまま、I/O待ちのブロッキングタイムを他のリクエスト処理に有効活用できます。
—
まとめ:PHPの未来を見据えて
PHPは、単なる「HTMLを生成するスクリプト言語」から、独自のイベントループとFiberを持ち、高スループットなネットワークサービスを構築できる「モダンなプラットフォーム」へと進化しました。
- Generator は「データを流すための軽量パイプ」。
- Fiber は「処理を一時停止・再開するための独立したコンテキスト」。
この2つの違いをZend VMのメモリモデルや実行コンテキストの視点から理解できれば、あなたの書くPHPコードは、単に「動くコード」から「内部構造まで最適化された美しいアーキテクチャ」へと昇華するでしょう。
ぜひ、日々の開発のなかにFiberの概念を取り入れ、PHPの新しい可能性を肌で感じてみてください。次回の設計で壁にぶつかったとき、この知見が必ずやあなたの羅針盤となるはずです。