こんにちは。大規模なWebシステムの設計やパフォーマンスチューニングで、日夜頭を悩ませていることと思います。
他のモダンな言語、例えばGoやNode.js、Javaあたりを経験してきた優秀なエンジニアほど、PHPの「1リクエスト完結型の世界観」や「暗黙的なメモリ管理」の裏側で何が起きているのか気になりますよね。特に、マイクロサービス間の通信やRedisなどのインメモリキャッシュへ大量のデータを流し込むとき、「シリアライズ・デシリアライズのコスト」で壁にぶつかるのは、アーキテクトとして非常に自然な通過点です。
今回は、PHPの内部データ構造(Zend Engineの心臓部である`zval`と`HashTable`)の視点に立ち戻り、「JSON」と「igbinary」という二大シリアライズ方式が、CPUとメモリにどのような負荷を強いているのかを紐解いていきましょう。
ここを理解すると、PHPというエンジンの裏側がぐっとクリアに見えてきますよ。
—
1. なぜPHPのシリアライズは一筋縄ではいかないのか?
私たちが普段何気なく書いているPHPの配列やオブジェクト。これらは、メモリ上では単なる連続した領域には存在しません。Zend Engineの内部では、すべての変数は`zval`(Zend Value)という構造体で表現され、データ型や参照カウンタ、そして実際の値やポインタが複雑に絡み合っています。
特にPHPの配列(Array)の実体は、順序付きのハッシュマップである`HashTable`です。文字列のキーであれ、整数のインデックスであれ、内部的にはハッシュ値の計算と衝突解決のためのポインタ配列を渡り歩いています。
この「Zend Engine特有の複雑なメモリ上のグラフ構造」を、外部(ネットワークやストレージ)に出力するためには、何らかの形に「平坦化(シリアライズ)」しなければなりません。ここで選ぶ方式によって、CPUサイクルとメモリ帯域の消費量が劇的に変わるのです。
—
2. JSON: 可読性の代償としての「テキスト変換コスト」
Web業界の共通言語であるJSON。API通信のデファクトスタンダードであり、人間が読めるという圧倒的なメリットがあります。しかし、PHPの内部構造というミクロな視点で見ると、JSONへの変換(`json_encode`)と復元(`json_decode`)は、実は極めて重い処理です。
内部で何が起きているのか?
1. 型変換のオーバーヘッド: PHPの動的な型(`zval`)を走査し、JavaScript的なプリミティブ型(文字列、数値、配列、ブール値)へと一つずつキャストしていく必要があります。
2. 文字列表現へのシリアライズ(CPUバウンド): 例えば整数値の `12345678` を出力する場合、CPUはそれをメモリ上のバイナリから `”12345678″` という8バイトの文字列へASCII/UTF-8変換し、アロケートし直す作業を強制されます。
3. デコード時のパース地獄: `json_decode` が呼ばれると、C言語レベルで巨大な文字列表現をトークナイズし、AST(抽象構文木)のようなパース処理を経て、再びPHPの `zval` や `HashTable` をメモリ上に再構築します。
文字列としてネットワークを流れるため、ペイロード(データサイズ)は肥大化しやすく、ネットワークI/Oのボトルネックにも直結します。
—
3. igbinary: バイナリの魔術が生む圧倒的な効率性
一方で、PECL拡張として提供されている `igbinary` は、PHPの内部構造に特化して設計されたバイナリシリアタイザです。PHP以外の言語とは基本的にデータを共有できませんが、「PHPからPHPへ(あるいはRedis等のキャッシュ層へ)」データを預けるユースケースにおいては、無類の強さを発揮します。
内部で何が起きているのか?
1. キーの重複排除(ストリングプール): JSONや標準の `serialize()` では、連想配列のキー(例: `”user_id”`, `”status”`, `”created_at”`)がレコードごとに何度もシリアライズされ、データサイズを圧迫します。しかし `igbinary` は、一度登場したキー文字列をメモリ上のテーブルに保持し、2回目以降は「小さなID(整数ポインタ)」に置き換えて表現します。
2. バイナリ直書き: 数値データをわざわざ文字列表現に直さず、C言語のネイティブなバイナリ表現のままパックします。これにより、CPUのエンコード・デコードコストが劇的に削減されます。
—
4. 実測:メモリ消費とCPU負荷の比較シミュレーション
百聞は一見にしかず。次のような、階層構造を持つやや大きめの連想配列(10,000件のユーザーメタデータを含む構造)を想定してみましょう。
/
function createDummyData(int $count): array {
$data = [];
for ($i = 0; $i < $count; $i++) {
$data[] = [
'user_id' => $i,
‘uuid’ => ‘123e4567-e89b-12d3-a456-426614174000’,
‘username’ => ‘architect_user_’ . $i,
‘email’ => ‘user_’ . $i . ‘@example.com’,
‘roles’ => [‘admin’, ‘editor’, ‘subscriber’],
‘attributes’ => [
‘login_count’ => 42,
‘is_active’ => true,
‘score’ => 98.5,
],
];
}
return $data;
}
$dataset = createDummyData(10000);
// — 1. JSON方式 —
$timeStart = hrtime(true);
$memStart = memory_get_usage();
$jsonPayload = json_encode($dataset, JSON_UNESCAPED_UNICODE);
$jsonDecoded = json_decode($jsonPayload, true);
$jsonTime = hrtime(true) – $timeStart;
$jsonMem = memory_get_usage() – $memStart;
$jsonSize = strlen($jsonPayload);
// — 2. igbinary方式 —
// ※要 ext-igbinary
$timeStart = hrtime(true);
$memStart = memory_get_usage();
$igBinaryPayload = igbinary_serialize($dataset);
$igBinaryDecoded = igbinary_unserialize($igBinaryPayload);
$igTime = hrtime(true) – $timeStart;
$igMem = memory_get_usage() – $memStart;
$igSize = strlen($igBinaryPayload);
// 結果の出力(単位の正規化)
echo “=== JSON 方式 ===\n”;
echo “処理時間: ” . number_format($jsonTime / 1_000_000, 2) . ” ms\n”;
echo “データサイズ: ” . number_format($jsonSize / 1024, 2) . ” KB\n\n”;
echo “=== igbinary 方式 ===\n”;
echo “処理時間: ” . number_format($igTime / 1_000_000, 2) . ” ms\n”;
echo “データサイズ: ” . number_format($igSize / 1024, 2) . ” KB\n”;
このコードが示すエンジニアリングの真実
実際にこのコードをCGI/CLI環境で実行してみると、次のような傾向がはっきりと現れます。
- データサイズ: `igbinary` は JSON に比べて 30%〜50%程度コンパクト に収まることが多いです。これはキー名の重複排除(ストリングプール)が完璧に機能している証拠です。
- CPU・処理時間: JSONのテキストパース・生成コストに比べ、`igbinary` はメモリ上のブロックを素早くストリームに流し込む(あるいは復元する)ため、処理時間が大幅に短縮(しばしばJSONの半分以下)されます。
RedisなどのKVSに複雑なドキュメント構造のデータをキャッシュする場合、`igbinary` を有効化するだけで、ネットワーク帯域の節約と、Redisサーバー・PHPアプリサーバー双方のCPU負荷軽減を同時に達成できるのです。
—
5. アーキテクトが下すべき選定基準
では、実際のシステム設計においてどのように使い分けるべきでしょうか。指針は極めてシンプルです。
1. 外部システム(React/Vueのフロントエンド、外部API、モバイルアプリ)との通信
- 選択:JSON一択
- 理由:異言語間での相互運用性が絶対条件であるため。パフォーマンスの最適化は、Gzip/Brotli圧縮や適切なキャッシュヘッダーの活用で担保します。
2. 内部マイクロサービス間通信(gRPCが使えないレガシーなPHP間API)、またはRedis等のインメモリキャッシュ
- 選択:igbinaryを強く推奨
- 理由:送信側も受信側もPHPであるならば、異言語互換性を捨てる代わりに、圧倒的な省メモリとCPUサイクルの節約という果実を確実に得られます。
—
最後に
私たちが書くPHPのコードは、抽象的な概念の羅列ではありません。その背後では、Zend Engineがミリ秒単位の時間を削り、バイト単位のメモリをやり取りしながら懸命に動いています。
「なぜこのシリアライズ方式を選ぶのか」をメモリとCPUの挙動から逆算して説明できるようになると、あなたの作るWebシステムは、トラフィックが急増したときにもビクともしない、美しく強靭な基盤へと生まれ変わります。
PHPの裏側が綺麗に見えると、コーディングがもっと楽しく、エキサイティングになりますよ。次の設計の引き出しとして、ぜひ `igbinary` の選択肢を検討してみてくださいね。