PHPジェネレータ(Generator)で大量データ処理のメモリ使用量を削減する仕組みと実践方法

PHP

PHPで大量のデータを処理する場合、通常の配列処理ではメモリ不足が発生することがあります。特にデータベースから数十万件、数百万件のレコードを取得して処理するような場面では、すべてのデータをメモリ上に保持する方法は大きな負荷になります。

このような大量データ処理で役立つ機能がPHPのジェネレータ(Generator)です。ジェネレータはデータを一度にすべて保持せず、必要なタイミングで1件ずつ生成することで、配列処理と比較して大幅にメモリ使用量を削減できます。

通常の配列処理でメモリ使用量が増える理由

PHPの配列は便利ですが、内部的には単純な連続メモリ領域ではなく、キーや値を管理するための情報を多く保持しています。そのため、大量のデータを格納すると見た目以上にメモリを消費します。

例えば、データベースから100万件のユーザー情報を取得して配列に保存する場合、処理開始時点ですべてのデータがメモリ上に展開されます。

以下のような処理では、取得したデータ全体が保持され続けます。

<?php
$users = getUsers();

foreach ($users as $user) {
  // データ処理
}
?>

データ件数が少ない場合は問題ありませんが、大量データになるとメモリ使用量が急増し、Fatal error: Allowed memory size exhaustedのようなエラーにつながる可能性があります。

PHPジェネレータ(Generator)の基本的な仕組み

ジェネレータはyieldキーワードを使って作成します。通常の関数はreturnで値を返すと処理が終了しますが、yieldを使うと値を1つずつ返しながら途中状態を保持できます。

基本的な例は以下のようになります。

<?php
function numbers() {
  for ($i = 1; $i <= 5; $i++) {
    yield $i;
  }
}

foreach (numbers() as $number) {
  echo $number;
}
?>

この場合、5個の値を最初から配列として作成するのではなく、foreachで必要になったタイミングごとに値が生成されます。

配列処理とジェネレータのメモリ使用量の違い

通常の配列処理では、処理対象となるすべてのデータをメモリに保持します。一方、ジェネレータでは現在処理しているデータだけを保持し、次のデータは必要になった時点で生成します。

例えば100万件のCSVファイルを読み込む場合、配列処理ではファイル全体を読み込む必要があります。

<?php
$rows = file(‘large.csv’);

foreach ($rows as $row) {
  // 処理
}
?>

この方法では100万行分のデータがメモリ上に残ります。

一方、ジェネレータを利用すると1行ずつ読み込めます。

<?php
function readCsv($file) {
  $handle = fopen($file, ‘r’);

  while (($row = fgetcsv($handle)) !== false) {
    yield $row;
  }

  fclose($handle);
}

foreach (readCsv(‘large.csv’) as $row) {
  // 1行ずつ処理
}
?>

このように処理すれば、大きなファイルでも一定量のメモリで処理できます。

大量データ処理でGeneratorが効果的なケース

ジェネレータは、すべてのデータを同時に必要としない処理で特に効果を発揮します。

具体的には以下のようなケースがあります。

  • 大量のCSVファイル読み込み
  • データベースから大量レコードを順番に処理する処理
  • ログファイル解析
  • APIから取得した大量データの変換処理
  • バッチ処理や定期実行処理

例えばECサイトの商品データを100万件更新する処理では、全商品を配列に入れるより、ジェネレータで1件ずつ取得して更新する方が安定した処理になります。

データベース処理でGeneratorを利用する例

データベースから大量データを取得する場合も、ジェネレータは有効です。

例えばPDOを利用してデータを順番に取得する場合、以下のような形で実装できます。

<?php
function getUsers(PDO $pdo) {
  $stmt = $pdo->query(‘SELECT * FROM users’);

  while ($user = $stmt->fetch(PDO::FETCH_ASSOC)) {
    yield $user;
  }
}

foreach (getUsers($pdo) as $user) {
  // ユーザーごとの処理
}
?>

この方法では、すべてのユーザーデータを一括取得して配列化する必要がなくなります。

Generatorを使う際の注意点

ジェネレータは万能ではありません。データを何度も繰り返し利用したい場合や、ランダムアクセスが必要な場合には配列の方が適しています。

例えば、取得したデータの5番目や100番目へ自由にアクセスしたい場合、ジェネレータでは一度順番に処理する必要があります。

また、ジェネレータは一度消費すると基本的には再利用できません。そのため、処理内容に合わせて配列と使い分けることが重要です。

まとめ

PHPのGeneratorは、大量データ処理においてメモリ使用量を削減するために非常に有効な機能です。

通常の配列処理では大量のデータを一度にメモリへ展開しますが、Generatorではyieldによって必要な分だけデータを生成するため、処理中のメモリ消費を一定に保ちやすくなります。

特にCSV処理、ログ解析、データベースの大量更新、バッチ処理などでは大きな効果があります。ただし、すべての場面で配列より優れているわけではないため、データの利用方法に合わせて適切に使い分けることが重要です。

コメント

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