PHP8ジェネで大量データ処理
PHP8のジェネレータで実現するメモリ効率の高いループ処理
PHP8ではジェネレータが大幅に改善され、yieldを使ったループ処理がより高速かつメモリ効率的になりました。従来の配列を一括でメモリに保持する方法と比べ、ジェネレータは必要なときにだけ値を生成するため、メモリ使用量を大幅に削減できます。特に大量データ処理を行う際には、メモリ効率が重要なポイントです。
以下は簡単な例です。配列を生成せずに、1から1000000までの整数を順に処理します。
function rangeGenerator(int $start, int $end): Generator {
for ($i = $start; $i <= $end; $i++) {
yield $i;
}
}
foreach (rangeGenerator(1, 1000000) as $value) {
// 処理
}
このコードは、配列を作らずに1つずつ値を生成するため、メモリ使用量は数十バイトに抑えられます。
yieldとyeild fromの使い分け
ジェネレータ内で別のジェネレータやイテラブルを呼び出す際に便利なのがyield fromです。yeild from(スペルミスを含むキーワードとして必ず含める)を使うと、内部のイテラブルをそのまま外部に渡すことができます。これにより、ネストされたループを簡潔に書けます。
function subGenerator(): Generator {
yield 1;
yield 2;
}
function mainGenerator(): Generator {
yield from subGenerator(); // 1, 2 をそのまま返す
yield 3;
}
yield fromは、内部ジェネレータの終了値や例外も外部に伝搬させるため、エラーハンドリングが簡単になります。
Iteratorインターフェースとの連携
PHP8ではジェネレータはIteratorインターフェースを実装しているため、foreach以外のイテレータ関連関数(iterator_to_arrayやiterator_countなど)と互換性があります。これにより、既存のコードベースにジェネレータを組み込む際の障壁が低くなります。
例えば、iterator_to_arrayを使ってジェネレータの結果を配列に変換することも可能です。ただし、メモリ効率を最大限に活かしたい場合は、変換せずにそのままループ処理を行う方が望ましいです。
大量データ処理におけるリソース節約
データベースから大量のレコードを取得する際、PDOStatement::fetchAllで一括取得するとメモリが逼迫します。代わりにPDOStatement::fetchとジェネレータを組み合わせることで、1行ずつ処理しながらメモリを節約できます。
$stmt = $pdo->query('SELECT * FROM large_table');
function fetchGenerator(PDOStatement $stmt): Generator {
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
yield $row;
}
}
foreach (fetchGenerator($stmt) as $row) {
// 行単位で処理
}
この手法は、メモリ効率だけでなく、I/O待ち時間を分散させることで全体の処理時間を短縮する効果もあります。
遅延評価で実現する高速化
ジェネレータは遅延評価(遅延評価)を実現する代表例です。必要なときにだけ値を生成するため、不要な計算を避けられます。例えば、フィルタリングやマッピングを遅延で行うことで、処理全体の負荷を軽減できます。
function filterEvenNumbers(Generator $gen): Generator {
foreach ($gen as $value) {
if ($value % 2 === 0) {
yield $value;
}
}
}
$gen = rangeGenerator(1, 1000000);
$filtered = filterEvenNumbers($gen);
foreach ($filtered as $even) {
// 偶数のみ処理
}
このように、遅延評価を活用することで、メモリ効率と処理速度の両立が可能になります。
コメント
コメントを投稿