この記事は「ウィルゲート Advent Calendar 2023」の 2 日目の記事です。
こんにちは!ウィルゲート開発室の佐々木です。
みなさんは普段メモリを意識してコーディングしていますか?
今回はforeachなどのループ処理でメモリ節約に効果的なGeneretorについてご紹介します!
はじめに
大量データを配列に格納してループ処理する...
みなさんはこのような処理に見覚えはありませんか?
よく目にする処理だと思いますが、大量のデータを処理するには大量のメモリを必要とします。 こうした処理が積み重なってメモリオーバーが発生してしまった!という方も多いのではないでしょうか。
実は私もそうした経験があります。
そこで私がおすすめしたいのが、PHPのGeneratorという機能です!
Generatorとは
Generatorとは、メモリを節約しながら大量データを扱うことができる関数のことです。
公式ドキュメントでは以下のように説明されています。
ジェネレータを使うと、foreach でデータ群を順に処理するコードを書くときに メモリ内で配列を組み立てなくても済むようになります。 メモリ内で配列を組み立てると memory_limit を越えてしまうかもしれないし、 無視できないほどの時間がかかってしまうかもしれません。 配列を作る代わりに、ジェネレータ関数を書くことになります。 (https://www.php.net/manual/ja/language.generators.overview.phpより引用)
通常、関数は一度に全ての結果を返すため、対象データが大きければ大きいほどメモリを圧迫してしまいます。
一方Generatorは、関数内で必要なだけ値を生成するため、メモリの使用量が少なく済みます。
そのため、大量データを処理する際にとても効果的です。
使い方
Generetor関数では、yieldを使用して値を生成し、呼び出し元に値を返します。(returnのような役割)
関数が呼び出されると、Generetor内の最初のyieldまで実行し、その値が呼び出し元に返されます。そして、次に呼び出された時には、前回のyieldの次の行から再開されます。
function sampleGenerator() {
yield 1;
yield 2;
yield 3;
}
foreach (sampleGenerator() as $value) {
echo $value . ' ';
}
上記の例では、sampleGenerator関数がGenerator関数となります。
foreachのループを通してGeneratorで値が生成され 、ループが進むたびにyieldされた値が表示されます。
// 出力例
1 2 3
どのくらいメモリを節約できる?
実際に通常の関数とGenerator関数を使用したループ処理を比較して、どの程度メモリ使用量に差があるのかを確認していきます。
実行する処理はそれぞれ以下の通りです。
// 配列を生成して返す
function getArray()
{
$array = [];
for ($i = 0; $i < 100000; $i++) {
$array[] = $i;
}
return $array;
}
foreach (getArray() as $value) {
echo $value;
}
// Generatorを使用して値を返す
function arrayGenerator()
{
for ($i = 0; $i < 100000; $i++) {
yield $i;
}
}
foreach (arrayGenerator() as $value) {
echo $value;
}
それぞれを実行した際のmemory_get_peak_usage()
の実行結果が以下となります。
ループ対象 | 通常の関数 | Generator関数 |
---|---|---|
メモリ使用量 | 4.4MB | 0.38MB |
Generator関数を使用した処理の方がメモリ使用量が圧倒的に低いのが分かりますね!
上記のことから、メモリの節約に効果的と言えます。
おわりに
Generatorは簡単に導入でき、メモリ節約の効果も大いに期待できます。
実際に、私もプロダクトへGeneratorを導入してメモリオーバーを解決することができました。
メモリオーバーにお悩みの方は、ぜひ検討してみてください!
「ウィルゲート Advent Calendar 2023」、翌日は田島さんによる「AWS EMRによる分散処理実行で苦戦した3つのこと」です。 お楽しみに!