こんにちは。大規模なWebサービスの裏側で、PHPのZendエンジンが唸りを上げる音を聞いたことはありますか?
日々の開発で何気なく使っている `json_encode()` や `serialize()`。これらは単なる「便利なデータ変換関数」ではありません。PHPプロセスがメモリをどう喰らい、CPUサイクルをどう消費してネットワークの向こう側へデータを送り出しているか──その生死を分ける最前線です。
今回は、他の言語(Ruby, Python, Node.jsなど)の経験はあるけれど、「PHPの内部でデータがどう扱われているのか、そろそろ一段深いレイヤを理解したい」というあなたに向けて、JSONと igbinary のメモリ配置とCPU負荷の真実を、Zendエンジンの内部構造に踏み込みながら優しく紐解いていきます。
ここを理解すれば、数百万リクエストを捌くマイクロサービス間通信や、Redisなどのインメモリデータストアを極限まで最適化する視点が綺麗に見えてきますよ。
—
1. なぜ「シリアライズ」がPHPのボトルネックになるのか
Webアプリケーションがスケールするにつれて、必ず直面するのが「データの永続化と転送」の壁です。
PHPは「共有ード・ナッシング(Shared-Nothing)」アーキテクチャを採用しています。つまり、1つのリクエストが終われば、そのリクエスト内で生成されたすべてのメモリ(Zval)は容赦なく解放されます。
しかし、現代のWebシステムは単体では完結しません。
- セッション情報の永続化(Redis / Memcached)
- マイクロサービス間のAPI通信(gRPC, REST)
- 重い計算結果のキャッシュ(APC / Redis)
これらを行うためには、PHPのメモリ空間(Zend VM上の複雑なツリー構造)を、一度「ただのバイト列(フラットなバイナリ)」に変換し、再び元に戻す(デシリアライズ)必要があります。ここに膨大なCPUサイクルとメモリ割り当てのオーバーヘッドが隠されています。
—
2. JSONの絶望:なぜテキストフォーマットは重いのか
まずは、誰もが使うお馴染みの `json_encode()` / `json_decode()` です。JSONは人間が読めて、言語中立であるという神聖なメリットを持っていますが、PHPエンジンの内部処理という観点からは、実は非常に贅沢(非効率)な代物です。
Zend VM内部でのJSONの旅
1. Zvalの走査: PHPの配列やオブジェクトは、内部で `HashTable`(ハッシュテーブル)という非常に洗練された、しかしポインタの網の目のようなデータ構造で保持されています。
2. 型変換とエスケープ: 各要素を `zval` から取り出し、C言語の文字列へとシリアライズしていきます。この時、数値であっても文字列表現(ASCII/UTF-8)へと変換され、特殊文字のエスケープ処理が走ります。
3. メモリの断片化: テキストフォーマットの生成は、可変長の文字列結合の連続です。Zendメモリマネージャー(emalloc)が微小なメモリブロックの確保と解放を狂ったように繰り返します。
結果として生成されるのは、「ただの文字列」です。
例えば、次のような連想配列を考えてみましょう。
$data = [
‘user_id’ => 128492,
‘username’ => ‘architect_z’,
‘roles’ => [‘admin’, ‘developer’, ‘security_1st’],
‘metadata’ => [‘login_count’ => 1420, ‘last_active’ => 1718000000]
];
これをJSONにすると、キー名(`”user_id”`, `”username”` …)というメタデータがすべてのレコードに文字として重複して含まれます。1万件のレコードをRedisに放り込むとき、このキー名の文字列が何メガバイト分も重複してネットワークとメモリを圧迫することになります。
—
3. igbinaryの救済:C構造体のバイナリ直撃
ここで登場するのが、PECL拡張である igbinary です。
igbinaryは、PHPのデータを「バイナリ形式」でシリアライズするための専用モジュールです。標準の `serialize()` よりも圧倒的にコンパクトで高速なことで知られていますが、その内部挙動はZendエンジンの仕様と深く結びついています。
igbinaryが圧倒的に速く、小さくなる理由
- キー名の重複排除(Header Compression):
JSONや標準の `serialize()` と異なり、igbinaryは同一のリクエスト(あるいはシリアライズデータ内)で出現するキー名や文字列のハッシュをキャッシュします。2回目以降の出現時は、文字列そのものではなく「インデックス番号(ID)」を参照するため、データサイズが劇的に縮小します。
- Zvalのダイレクト変換:
PHPの内部データ型(IS_LONG, IS_STRING, IS_ARRAYなど)を、極めて効率的なバイナリレイアウトに直接マッピングします。無駄な文字列表現への変換(ToStringキャスト)を極力バイパスするため、CPUのキャッシュヒット率が跳ね上がります。
—
4. 実行コードで見る:メモリ消費量とパフォーマンスの比較
百聞は一見に如かず。実際にどれほどの違いが出るのか、擬似的な大規模データ構造を用いて検証してみましょう(※実行には `ext-igbinary` が必要です)。
/
function generateLargeDataset(int $count): array {
$dataset = [];
for ($i = 0; $i < $count; $i++) {
$dataset[] = [
'id' => $i,
‘uuid’ => ‘550e8400-e29b-41d4-a716-4466554400’ . ($i % 10),
‘name’ => ‘Engineer ‘ . $i,
‘email’ => ‘user_’ . $i . ‘@zengine.internal’,
‘is_active’ => ($i % 2 === 0),
‘permissions’ => [‘read’, ‘write’, ‘execute’],
‘created_at’ => time()
];
}
return $dataset;
}
$data = generateLargeDataset(10000);
// — 1. JSONによるシリアライズ —
$startMemoryJson = memory_get_usage(true);
$startTimeJson = microtime(true);
$jsonString = json_encode($data, JSON_UNESCAPED_UNICODE);
$endTimeJson = microtime(true);
$endMemoryJson = memory_get_usage(true);
$jsonSize = strlen($jsonString);
$jsonTime = ($endTimeJson – $startTimeJson) 1000;
// — 2. igbinaryによるシリアライズ —
// ※要 pecl install igbinary
if (!extension_loaded(‘igbinary’)) {
die(“igbinary extension is not loaded.\n”);
}
$startMemoryIgb = memory_get_usage(true);
$startTimeIgb = microtime(true);
$igbString = igbinary_serialize($data);
$endTimeIgb = microtime(true);
$endMemoryIgb = memory_get_usage(true);
$igbSize = strlen($igbString);
$igbTime = ($endTimeIgb – $startTimeIgb) 1000;
// — 結果出力 —
echo “=== シリアライズ性能比較 (10,000件) ===\n”;
printf(“JSON : साइज = %.2f KB | 処理時間 = %.2f ms\n”, $jsonSize / 1024, $jsonTime);
printf(“Igbinary : サイズ = %.2f KB | 処理時間 = %.2f ms\n”, $igbSize / 1024, $igbTime);
printf(“サイズ削減率: %.1f%%\n”, (1 – ($igbSize / $jsonSize)) 100);
このコードが示すエンジニアリングの示唆
手元の環境で走らせてみると一目瞭然ですが、igbinaryはJSONに比べてデータサイズが数分の一(場合によっては20〜30%程度)に圧縮されます。
なぜこれが重要か?
Webアプリケーションのボトルネックの多くは「CPU演算そのもの」よりも、「ネットワーク帯域」と「Redis/Memcachedのメモリ容量」にあります。
データサイズが小さければ小さいほど、RedisとのTCP通信におけるパケット数が減り、キャッシュサーバー側のメモリ圧迫を防ぐことができます。数百万ユーザーを抱えるシステムでは、この数バイトの積み重ねがインフラコストの数百万単位の差となって跳ね返ってきます。
—
5. では、いつでも igbinary を使えばいいのか?(アーキテクトの判断基準)
「それなら全部 igbinary に置き換えれば最強だね!」と思ったあなた、少し待ってください。ここが腕の見せ所です。
優れたアーキテクトは、技術のメリットだけでなく、トレードオフ(代償)を正確にコントロールします。
igbinaryを採用すべきシーン
1. PHP製マイクロサービス間の内部通信
- 送信側も受信側もPHP(Zend VM)であることが確約されている場合。
2. Redis / Memcached などの内部キャッシュストア
- セッションデータや、巨大なオブジェクトグラフを一時保存し、PHPだけでデシリアライズする場合。
JSON(あるいはMessagePackなど)を選択すべきシーン
1. 外部の異種言語システムとの連携
- Node.jsのフロントエンドや、Go言語のマイクロサービス、PythonのAI基盤とデータをやり取りする場合。バイナリ(igbinary)はPHPの内部構造に依存しているため、他言語からは原則として読めません。
2. ログ基盤への出力や長期保管
- 人間がデバッグ時にテキストとして直接読めたり、Elasticsearchなどの全文検索エンジンに流し込んだりする場合。
—
まとめ:PHPの裏側を見通す目を養う
私たちが日常的に書くたった1行の `json_encode($data)` や `igbinary_serialize($data)` の裏側では、Zendエンジンがメモリを確保し、CPUが必死に型を解釈し、OSがバッファをやり取りしています。
「なぜこの関数は遅いのか」「なぜこのキャッシュはメモリを食うのか」に直面したとき、表面的なフレームワークの仕様書を閉じて、PHPのコアやメモリ構造(HashTableとZval)に思いを馳せてみてください。
技術の本質を理解したあなたなら、きっと綺麗で無駄のない、美しいシステムを設計できるはずです。
さあ、次のコードブロックを開いて、最高にパフォーマンスの出るアーキテクチャを実装しに行きましょう。