【入門編】PHPのジェネレータ(Generator)とFiberのメモリ効率比較:遅延評価とリソース消費の物理的差異 – PHPコア・内部エンジンと高速化・並行処理の極意解析バイブル

こんにちは。PHPの裏側を覗く旅へようこそ。

普段何気なく書いているPHPのコードですが、1リクエストのライフサイクルの中で、ZendエンジンはC言語レベルのメモリ管理とオペコードの実行を猛烈なスピードでこなしていますよね。

今回は、モダンPHPにおける「遅延評価と非同期の切り札」であるジェネレータ(Generator)とFiber(ファイバー)を取り上げます。「名前は聞いたことがあるけれど、メモリの観点で何がどう違うの?」という疑問を持つあなたへ、Zend VMの内部構造とメモリ空間の動きにまで踏み込んで、その物理的な差異を優しく紐解いていきましょう。

ここを理解すると、PHPのメモリ効率の限界を突破する設計が見えてきますよ。

—

1. そもそもPHPにおける「実行コンテキスト」とは何か?

PHPのスクリプトが実行されるとき、Zendエンジンは関数やメソッドの呼び出しごとに「スタックフレーム(`zend_execute_data`)」という構造体をヒープ上にメモリ確保します。通常の関数であれば、実行が終わればこのフレームは破棄され、メモリは解放されます。

しかし、GeneratorやFiberはこの「実行途中の状態」を宙吊りにし、後から再開させるという魔術を行います。この魔術の裏側で、メモリがどう扱われているのかが今回の核心です。

—

2. Generatorの正体:ただの「イテレータの皮を被ったステートマシン」

まずはGeneratorからです。数百万件のレコードをDBから読み込むときなどにお世話になりますよね。

Generatorの心臓部は、Cのソースコード上では `zend_generator` という構造体です。驚くべきことに、Generatorは独自のコールスタックを持ちません。

メモリ消費の物理的特徴

Generatorが保持するのは、以下のわずかなデータだけです。
1. 実行中の関数に対応する `zend_execute_data`(スタックフレーム)のコピー
2. ローカル変数や引数の状態を保持するHashTable
3. 現在の実行位置を示すオペコード(Opcodes)のポインタ

これらはすべてヒープ上に確保されますが、通常の関数呼び出しと違い、「親から子へ、子から孫へ」という深さ方向のスタックチェーンを持たず、フラットな状態保持にとどまります。

コードで見るGeneratorの省メモリ性

「スタックを持たず、単一のフレームの変数のスナップショットだけでループを回り続けるから」に他なりません。

—

3. Fiberの正体:真の「スタックフル・ファイバー(協調的マルチスレッド)」

では次に、PHP 8.1で導入されたFiberはどうでしょうか。「PHPでも非同期処理ができる!」と話題になりましたが、そのメモリ構造はGeneratorとは根本的に異なります。

Fiberは、独自のコールスタック(Fiber Stack)を持ちます。

メモリ消費の物理的特徴

Fiberの中で関数がさらに別の関数を呼び出し、その中でさらに……とネストした場合、Fiberはその呼び出し階層全体のコールスタック(実行コンテキストのツリー)丸ごとを保持します。

そのため、Generatorに比べて初期のメモリオーバーヘッドが大きくなります。

コードで見るFiberの動作とメモリの重み

start();
echo “受け取った値: {$value}\n”;

// 外側からFiberを再開
$fiber->resume();

function deepFunctionCall(string $str): string {
// この関数呼び出しの階層もFiberのスタック内に保持される
return “加工されたデータ: ” . strtoupper($str);
}

Fiberは、コールスタック全体を丸ごと「オブジェクト」としてヒープ上に抱え込みます。そのため、深いネストや複雑な関数呼び出しをFiber内部で行うと、消費するメモリ量は比例して増大します。

—

4. Generator vs Fiber:メモリ効率と構造の比較マトリクス

ここまでの話を、アーキテクトの視点でスパッと整理してみましょう。

| 評価項目 | Generator(ジェネレータ) | Fiber(ファイバー) |
| :— | :— | :— |
| スタックの有無 | なし(単一フレームの変数保持のみ) | あり(独自のコールスタック全体を保持) |
| 主な目的 | データの遅延評価(Lazy Evaluation)とストリーミング | 処理の一時停止・再開(協調的マルチタスキング) |
| メモリフットプリント | 極小(数KB〜) | 中程度(スタック領域の分だけ大きく、数KB〜MB) |
| 制御の方向 | 呼び出し元へ「値」を返す(Producer) | 任意の深さから処理を中断・再開する(Continuation) |
| 適したユースケース | 大規模ファイルの読み込み、DBのチャンク処理 | 非同期I/O、イベントループ、複雑な非同期フロー制御 |

—

5. 現場でどう使い分けるべきか? アーキテクトからの提言

「Fiberのほうが多機能なら、全部Fiberで書けばいいのでは?」という声が聞こえてきそうですが、それはPHPの実行モデルにおいてアンチパターンになり得ます。

1. データを流すだけなら、絶対に Generator を選ぶこと

数万件のレコードを処理したり、巨大なCSVをパースしたりするだけなら、Fiberを使う理由は1ミリもありません。Generatorを使い、メモリ上に常駐するオブジェクトの数を最小限に抑えるべきです。Fiberを使うと、無駄なスタック領域の確保コストを払うことになります。

2. 「非同期の待ち時間」を制御したいなら Fiber を選ぶこと

例えば、外部APIへのリクエストやデータベースの非同期クエリなど、「処理を中断して、別のタスクを動かし、準備ができたら元の場所に戻りたい」という制御フローの複雑さを解決したいときはFiberの出番です。AmpやReactPHPなどのモダンな非同期フレームワークの裏側では、このFiberが効率的なイベントループを支えています。

—

まとめ

いかがでしたでしょうか?

  • Generatorは、「状態を記憶するスリムなイテレータ」であり、メモリを節約しながらデータを流すためのもの。
  • Fiberは、「コールスタックをごっそり保存するコンテキストの持ち主」であり、複雑な非同期処理の流れを制御するためのもの。

PHPの内部でメモリがどのように確保され、動いているのかをイメージできるようになると、コードを書くときの「重さ」が感覚として手に取るように分かるようになります。

この知見を武器に、ぜひ無駄のない、美しくスケーラブルなPHPアプリケーションを設計してくださいね。それでは、また次の深いレイヤーでお会いしましょう!

タイトルとURLをコピーしました