Slide 1

Slide 1 text

X(旧twitter): @for__3 #phpcondo_yasai PHPとLaravelで使える ジェネレータを使った⼤量データ処理の パフォーマンス改善 2024/01/11 PHPカンファレンス北海道2024 全然野菜 @zoe 1

Slide 2

Slide 2 text

X(旧twitter): @for__3 #phpcondo_yasai 2

Slide 3

Slide 3 text

X(旧twitter): @for__3 #phpcondo_yasai 3 株式会社ウィルゲート 9年⽬ VPoE やってること - 教育/採⽤/PM/SRE/インフラ 興味あること - オブザーバビリティ/⾃動化/開発⽣産性/PHP 執筆 - 【第3回】Dockerで実現! 効率的で⾼速な開発環境 ……makeコマンド⼀発でできる! - 【最終回】パフォーマンスチューニングをしよう ……PHP 8でXdebugとWebgrindを使ってプロファイ リング 池添 誠(いけぞえ まこと)

Slide 4

Slide 4 text

X(旧twitter): @for__3 #phpcondo_yasai スライドはXで公開してます 4 ※コードが読みづらい部分があるので必要に応じて⼿元で確認してください

Slide 5

Slide 5 text

X(旧twitter): @for__3 #phpcondo_yasai 参考実装レポジトリ https://github.com/ikezoeMakoto/example-laravel-lazy-co llection/ 5

Slide 6

Slide 6 text

X(旧twitter): @for__3 #phpcondo_yasai 6

Slide 7

Slide 7 text

X(旧twitter): @for__3 #phpcondo_yasai ジェネレータ? 7

Slide 8

Slide 8 text

X(旧twitter): @for__3 #phpcondo_yasai 8

Slide 9

Slide 9 text

X(旧twitter): @for__3 #phpcondo_yasai よくわからん 9

Slide 10

Slide 10 text

X(旧twitter): @for__3 #phpcondo_yasai 任意の個数の配列を作る関数を考える 10 function makeList($n) { $list = []; for ($i = 0; $i <= $n; $i++) { $list[] = $i; } return $list; } echo "array:"; $array = makeList($limit); foreach ($array as $item) { echo $item . ','; } 出力> array:0,1,2,3,4,5,6,7,8,9,10,11,12,1 3,14,15,16,17,18, 19,20,21,22,23,24,25,26,27,28,29,30, 31,32,33,34,35,36, 37,38,39,40,41,42,43,44,45,46,47,48, 49,50,51,52,53,54, 55,56,57,58,59,60,61,62,63,64,65,66, 67,68,69,70,71,72, 73,74,75,76,77,78,79,80,81,82,83,84, 85,86,87,88,89,90, 91,92,93,94,95,96,97,98,99,100,

Slide 11

Slide 11 text

X(旧twitter): @for__3 #phpcondo_yasai generator版 11 function makeGen($n) { for ($i = 0; $i <= $n; $i++) { yield $i; } } echo "generator:"; $gen = makeGen($limit); foreach ($gen as $item) { echo $item . ','; } 出力> generator:0,1,2,3,4,5,6,7,8,9,10,11, 12,13,14,15,16,17,18,19,20,21,22,23, 24,25,26,27,28,29,30,31,32,33,34,35, 36,37,38,39,40,41,42,43,44,45,46,47, 48,49,50,51,52,53,54,55,56,57,58,59, 60,61,62,63,64,65,66,67,68,69,70,71, 72,73,74,75,76,77,78,79,80,81,82,83, 84,85,86,87,88,89,90,91,92,93,94,95, 96,97,98,99,100,

Slide 12

Slide 12 text

X(旧twitter): @for__3 #phpcondo_yasai ⽐較 12 function makeGen($n) { for ($i = 0; $i <= $n; $i++) { yield $i; } } echo "generator:"; $gen = makeGen($limit); foreach ($gen as $item) { echo $item . ','; } function makeList($n) { $list = []; for ($i = 0; $i <= $n; $i++) { $list[] = $i; } return $list; } echo "array:"; $array = makeList($limit); foreach ($array as $item) { echo $item . ','; } 配列版 generator版

Slide 13

Slide 13 text

X(旧twitter): @for__3 #phpcondo_yasai ⽐較 13 function makeGen($n) { for ($i = 0; $i <= $n; $i++) { yield $i; } } function makeList($n) { $list = []; for ($i = 0; $i <= $n; $i++) { $list[] = $i; } return $list; } 返却⽤の配列に⼊れて返す 値をそのままyieldで返す 配列版 generator版

Slide 14

Slide 14 text

X(旧twitter): @for__3 #phpcondo_yasai ⽐較 14 echo "generator:"; $gen = makeGen($limit); foreach ($gen as $item) { echo $item . ','; } echo "array:"; $array = makeList($limit); foreach ($array as $item) { echo $item . ','; } 配列版 generator版 配列から順次、値を取り出す generatorから順次yieldの値を取り出す

Slide 15

Slide 15 text

X(旧twitter): @for__3 #phpcondo_yasai Generatorを使うことで 必要なタイミングで評価して取得できる 15

Slide 16

Slide 16 text

X(旧twitter): @for__3 #phpcondo_yasai ところで 16

Slide 17

Slide 17 text

X(旧twitter): @for__3 #phpcondo_yasai LaravelのCollection 便利ですよね 17

Slide 18

Slide 18 text

X(旧twitter): @for__3 #phpcondo_yasai Collectionでごにょごにょする関数を考える 18 public function case1($collection ) { $counter = 0; $collection = $collection ->filter(function ($i) { return $i % 2 === 0; }) ->values() ->map(function ($i) { return $i * 3; }) ->chunk(3) ->each(function () use (&$counter) { $counter++; }) ->take(10) ->collect(); $this->info("each呼び出し回数: $counter"); return $collection ; } public function handle() { $number = $this->argument('number'); // Collection $collection = Collection::times($number); $this->startPerformance(); $this->case1($collection); $this->endPerformance(); $this->printPerformance(); }

Slide 19

Slide 19 text

X(旧twitter): @for__3 #phpcondo_yasai Collectionでごにょごにょする関数を考える 19 public function case1($collection ) { $counter = 0; $collection = $collection ->filter(function ($i) { return $i % 2 === 0; }) ->values() ->map(function ($i) { return $i * 3; }) ->chunk(3) ->each(function () use (&$counter) { $counter++; }) ->take(10) ->collect(); $this->info("each呼び出し回数: $counter"); return $collection ; } public function handle() { $number = $this->argument('number'); // Collection $collection = Collection::times($number); $this->startPerformance(); $this->case1($collection); $this->endPerformance(); $this->printPerformance(); } 最終的に取得するデータは与える件数に依らず10件

Slide 20

Slide 20 text

X(旧twitter): @for__3 #phpcondo_yasai 実⾏結果 20 docker-compose run --rm php php artisan measure:collection 1000 each呼び出し回数: 167 Time(ms): 0.301952 Memory: 736 b Peak Memory: 19.22 mb docker-compose run --rm php php artisan measure:collection 10000 each呼び出し回数: 1667 Time(ms): 1.145516 Memory: 24.72 kb Peak Memory: 20.33 mb docker-compose run --rm php php artisan measure:collection 100000 each呼び出し回数: 16667 Time(ms): 30.102493 Memory: 249.4 kb Peak Memory: 30.86 mb docker-compose run --rm php php artisan measure:collection 1000000 Symfony\Component\ErrorHandler\Error\FatalError Allowed memory size of 134217728 bytes exhausted (tried to allocate 4194312 bytes) at vendor/laravel/framework/src/Illuminate/Collections/Collection.php:1317

Slide 21

Slide 21 text

X(旧twitter): @for__3 #phpcondo_yasai 実⾏結果 21 docker-compose run --rm php php artisan measure:collection 1000 each呼び出し回数: 167 Time(ms): 0.301952 Memory: 736 b Peak Memory: 19.22 mb docker-compose run --rm php php artisan measure:collection 10000 each呼び出し回数: 1667 Time(ms): 1.145516 Memory: 24.72 kb Peak Memory: 20.33 mb docker-compose run --rm php php artisan measure:collection 100000 each呼び出し回数: 16667 Time(ms): 30.102493 Memory: 249.4 kb Peak Memory: 30.86 mb docker-compose run --rm php php artisan measure:collection 1000000 Symfony\Component\ErrorHandler\Error\FatalError Allowed memory size of 134217728 bytes exhausted (tried to allocate 4194312 bytes) at vendor/laravel/framework/src/Illuminate/Collections/Collection.php:1317

Slide 22

Slide 22 text

X(旧twitter): @for__3 #phpcondo_yasai 実⾏結果 22 docker-compose run --rm php php artisan measure:collection 1000 each呼び出し回数: 167 Time(ms): 0.301952 Memory: 736 b Peak Memory: 19.22 mb docker-compose run --rm php php artisan measure:collection 10000 each呼び出し回数: 1667 Time(ms): 1.145516 Memory: 24.72 kb Peak Memory: 20.33 mb docker-compose run --rm php php artisan measure:collection 100000 each呼び出し回数: 16667 Time(ms): 30.102493 Memory: 249.4 kb Peak Memory: 30.86 mb docker-compose run --rm php php artisan measure:collection 1000000 Symfony\Component\ErrorHandler\Error\FatalError Allowed memory size of 134217728 bytes exhausted (tried to allocate 4194312 bytes) at vendor/laravel/framework/src/Illuminate/Collections/Collection.php:1317 \件数が増えるごとにメモリと実⾏時間が増加/

Slide 23

Slide 23 text

X(旧twitter): @for__3 #phpcondo_yasai LaravelのCollectionに Generatorの概念を⼊れたのが LazyCollection 23

Slide 24

Slide 24 text

X(旧twitter): @for__3 #phpcondo_yasai LazyCollectionで実装してみる 24 public function case2($collection ) { $counter = 0; $collection = $collection ->filter(function ($i) { return $i % 2 === 0; }) ->values() ->map(function ($i) { return $i * 3; }) ->chunk(3) ->tapEach(function () use (&$counter) { $counter++; }) ->take(10) ->collect(); $this->info("each呼び出し回数: $counter"); return $collection ; } public function handle() { $number = $this->argument('number'); // Lazy Collection $collection = LazyCollection::times($number); $this->startPerformance(); $this->case2($collection); $this->endPerformance(); $this->printPerformance(); }

Slide 25

Slide 25 text

X(旧twitter): @for__3 #phpcondo_yasai 実⾏結果 25 docker-compose run --rm php php artisan measure:lazy-collection 1000 each呼び出し回数: 10 Time(ms): 0.12558 Memory: 736 b Peak Memory: 19.54 mb docker-compose run --rm php php artisan measure:lazy-collection 10000 each呼び出し回数: 10 Time(ms): 0.116497 Memory: 736 b Peak Memory: 19.54 mb docker-compose run --rm php php artisan measure:lazy-collection 100000 each呼び出し回数: 10 Time(ms): 0.144288 Memory: 736 b Peak Memory: 19.54 mb docker-compose run --rm php php artisan measure:lazy-collection 1000000 each呼び出し回数: 10 Time(ms): 0.129414 Memory: 736 b Peak Memory: 19.54 mb \件数が増えてもメモリと実⾏時間が増加してない/

Slide 26

Slide 26 text

X(旧twitter): @for__3 #phpcondo_yasai ⼤量のデータに対して 逐次処理に変更できるものは LazyCollectionの出番かも? 26

Slide 27

Slide 27 text

X(旧twitter): @for__3 #phpcondo_yasai 参考資料 ● https://www.php.net/manual/ja/language.generators.o verview.php ○ 公式にサンプルコードと説明がいっぱいあるので読んでく ださい ● https://tadsan.fanbox.cc/posts/833584 ○ tadsan先⽣のジェネレータで無限を扱う話 27

Slide 28

Slide 28 text

X(旧twitter): @for__3 #phpcondo_yasai 参考資料 ● https://tech.willgate.co.jp/entry/2023/12/02/120000 ○ 弊社のテックブログでジェネレータの紹介をしたもの ● https://www.slideshare.net/techblogyahoo/phpioyield- phpcon2014 ○ ジェネレータを使ってI/Oを多重化する仕組みを作る 28

Slide 29

Slide 29 text

X(旧twitter): @for__3 #phpcondo_yasai 宣伝: 29