前回書いた記事で、Eloquent ORMにおけるChunkとCursorメソッドの挙動について、発行されるSQL文の観点から調べました。
そこで、まとめでも書きましたが、主にメモリ使用量を抑えるために使われるChunkとCursorのメソッドについて、実際にメモリ使用量を調べてみました。
メモリ使用量について、実際に計測してみようと思った
※ 僕自身、PHPのコードでメモリ使用量を意識したことはそこまでなく*1、どのように書けば特定の処理のメモリを調べることができるのか、というところから調べたのでその辺についても少し記載しています。
※ 本エントリで利用しているコードはすべて
にあります。
また、
- PHP7.1
- SQLite3
という環境で実行しています。
目次
PHPの最大メモリを変更する
100,000レコードものテーブルを全件取得しようとすると
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 4096 bytes)
と言って怒られてしまいました。 まず、手元のmacのPHPの設定を見にいきます。
$ php -i | grep php.ini Configuration File (php.ini) Path => /usr/local/etc/php/7.1 Loaded Configuration File => /usr/local/etc/php/7.1/php.ini $ cat /usr/local/etc/php/7.1/php.ini | grep memory_limit memory_limit = 128M
ということで128Mということがわかりました。
常に変更するのは避けたいので、ファイルのはじめに
ini_set('memory_limit', '256M');
と変更を加えておきました。
データセットを用意
歩行者移動支援サービスに関するデータサイト
- 国際観光ホテル整備法に基づいて登録されたホテル・旅館
こちらを利用して、約100,000件のレコードを用意しました。
php-sandbox/migration.php at master · zuckeyM-17/php-sandbox · GitHub
メモリ使用量を測定するスクリプト
php-sandbox/check.php at master · zuckeyM-17/php-sandbox · GitHub
php-sandbox/check-memory-usage/src/scripts at master · zuckeyM-17/php-sandbox · GitHub
<?php $capsule->getConnection()->enableQueryLog(); echo "CHECK MEMORY USAGE\n"; echo "==================\n"; $start = microtime(true); // 処理 $time = microtime(true) - $start; $memory = memory_get_peak_usage(true) / 1024 / 1024; echo "\n==================\n"; printf("time: %f memory: %f MB" . "\n", $time, $memory); $queryLog = $capsule->getConnection()->getQueryLog(); foreach ($queryLog as $i => $query) { echo 'Query' . ($i + 1) . ': ' . $query['query'] . PHP_EOL; }
このようなスクリプトを書いて、処理にかかった時間、最大メモリ使用量、発行したクエリを出力しました。
結果
結果は以下のようになりました。
$ php check.php all CHECK MEMORY USAGE ================== 縄文の宿まんてんル華耀亭や亭ザ・サンプラザート ================== time: 1.418852 memory: 172.007812 MB Query1: select * from "inns"
$ php check.php chunk10 CHECK MEMORY USAGE ================== 縄文の宿まんてんル華耀亭や亭ザ・サンプラザート ================== time: 59.299147 memory: 10.000000 MB Query1: select * from "inns" order by "inns"."id" asc limit 10 offset 0 Query2: select * from "inns" order by "inns"."id" asc limit 10 offset 10 ... Query9780: select * from "inns" order by "inns"."id" asc limit 10 offset 97790 Query9781: select * from "inns" order by "inns"."id" asc limit 10 offset 97800
$ php check.php chunk100 CHECK MEMORY USAGE ================== 縄文の宿まんてんル華耀亭や亭ザ・サンプラザート ================== time: 9.412318 memory: 4.000000 MB Query1: select * from "inns" order by "inns"."id" asc limit 100 offset 0 Query2: select * from "inns" order by "inns"."id" asc limit 100 offset 100 ~~~~~ Query978: select * from "inns" order by "inns"."id" asc limit 100 offset 97700 Query979: select * from "inns" order by "inns"."id" asc limit 100 offset 97800
$ php check.php chunk1000 CHECK MEMORY USAGE ================== 縄文の宿まんてんル華耀亭や亭ザ・サンプラザート ================== time: 1.720385 memory: 6.000000 MB Query1: select * from "inns" order by "inns"."id" asc limit 1000 offset 0 Query2: select * from "inns" order by "inns"."id" asc limit 1000 offset 1000 ~~~~ Query97: select * from "inns" order by "inns"."id" asc limit 1000 offset 96000 Query98: select * from "inns" order by "inns"."id" asc limit 1000 offset 97000
$ php check.php cursor CHECK MEMORY USAGE ================== 縄文の宿まんてんル華耀亭や亭ザ・サンプラザート ================== time: 1.620625 memory: 4.000000 MB Query1: select * from "inns"
処理 | 経過時間(s) | 最大メモリ使用量(MB) | クエリ数 |
---|---|---|---|
all | 1.418852 | 172.007812 | 1 |
chunk、10件ごと | 59.299147 | 10.000000 | 97790 |
chunk、100件ごと | 9.412318 | 4.000000 | 979 |
chunk、1000件ごと | 1.720385 | 6.000000 | 98 |
cursor | 1.620625 | 6.000000 | 1 |
まとめ
前回のブログで、
1回のクエリでアクセスしている分cursorのほうが実行速度は速いものの、メモリの使用量を抑えるという意味では、あまりにも膨大な量のレコードを処理する場合にはcursorよりもchunkのほうが有用なのではないかと考えられると思います。
と書きました。 しかしながら、上記の表を見る限り、Chunk1000以上にするとCursorとでは速度、メモリ使用量でそれほどの違いがなかったという結果になりました。
また、実行速度はさておき、Chunk1000とCursorとでは、
- 1回のループで作成するモデルインスタンスの数が違う
- 1回のクエリで取得するレコードの数が違う
にも関わらず、メモリ使用量が同じ値になったのは疑問が残りました。 Cursorの仕組みについて、Eloquentの実装方法について調べて行く必要がありそうに感じました。
検証方法や考察について間違いなどがあればどんどん指摘して欲しいです!! また、こういうこと検証、試したら面白い、調べて欲しいなどがあればTwitterやコメントなどで言っていただければ嬉しいです!!
*1:Circle CIでのテストがメモリのせいで失敗した時くらい