Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Qiita Night PHP 2023

Qiita Night PHP 2023

登壇資料です

ふわせぐ

January 26, 2023
Tweet

More Decks by ふわせぐ

Other Decks in Programming

Transcript

  1. • 竹下 拓秀 / ふわせぐ (@fuwasegu) • 株式会社ゆめみ / 21卒

    – コーポレートエンジニア • PHP / Laravel がメイン – Svelte(JS / TS),Rust Go 勉強中 • 長崎県出身 / 愛知県在住 • !(3 歳)の父 2 自己紹介 Qiita Night 〜PHP〜
  2. 宣伝 3 Qiita Night 〜PHP〜 • mpyw さんと二人で Laravel の

    ソースコードを読む会 • 隔週で YouTube Live で配信中 • バックナンバー • Model の with • Model の scope • SoftDelete • サービスコンテナ • ルートモデルバインディング • ExceptionHandler 次回: 2023-01-30 14:00:00
  3. • iterable = (Traversable | array) – 型のエイリアス – PHP7.1

    〜 PHP8.0 では疑似型 – それ以前は未定義 • foreach で反復処理ができるもの • iterator_to_array() に渡すと array になるもの – iterator_to_array() の引数 Traversable | array になったのは PHP8.2 から – それまでは Traversable のみだった 10 iterable とは Qiita Night 〜PHP〜
  4. 13 Qiita Night 〜PHP〜 iterable IteratorAggregate Iterator Generator extends extends

    extends implements 時間がないので説明は割愛
  5. Iterator とは • 反復処理中の状態を持つオブジェクト自身を 定義するinterface – 現在の要素を取得 – 現在のキーを取得 –

    次の要素に進む – 最初の要素に巻き戻し – 現在位置が有効かどうか 取得 Qiita Night 〜PHP〜 14
  6. IteratorAggregate とは • Iterator(Traversable) を作る interface • 自分自身が要素を持ったり状態を管理するわけ ではない Qiita

    Night 〜PHP〜 15 • getIterator() は新しいイテレータ を返す • foreach に IteratorAggregate を渡 すと勝手に getIterator() が呼ばれ る ポイント
  7. Generator とは • イテレータが簡単に作れるやつ – 本来 Iterator を自前で作る場合,インタフェースに定 義されたメソッドをそれぞれ実装する必要がある –

    Generator は yield するだけで良い • 遅延評価される(= 省メモリ) – 値(演算結果)が必要になったときに初めて評価 されるということ Qiita Night 〜PHP〜 16
  8. 愚直に実装してみる(方針) • 一気には読み込めないので一行ずつ読む – fopen() して fgetcsv() しながらループ • ループの中で

    条件に合致しなかったら読み 飛ばす • カウンタを作っておいてインクリメントしていく – 必要数集まったら break Qiita Night 〜PHP〜 21
  9. 22 Qiita Night 〜PHP〜 条件の数だけ if を並べる 1個の if にまとめる

    愚直に実装してみる(実装) 文字が小さいのは許して
  10. 27 Generator を使って書き直す Qiita Night 〜PHP〜 今回は Generator 関数を用いましたが,IteratorAggregate を

    使ってクラスごとに分割する方法もあります (Qiita で解説します!)
  11. • さっきと見た目は変わらない • 全て Generator なので CSV を一気に読み 込む必要も無くなった 28

    分割したメソッドで組み立て Qiita Night 〜PHP〜 CSV がどれだけ大きくても動作します
  12. • Generator を使わない場合 – Fatal error: Allowed memory size of

    134217728 bytes exhausted (tried to allocate 20480 bytes) • Generator を使った場合 – 2936 バイトで正常に終了 29 実験 Qiita Night 〜PHP〜 • 100 万行の CSV(77.2 MB)を入力 • CSV の読み込み 〜 フィルターにかかる使用メモリを計測 結果
  13. • Generator は yield が呼ばれるたびに値を生成する – Generator でない場合要素分のメモリが必要 – Generator

    が生成した値は消費されるので古いものは破棄される – つまり巻き戻せない(単一方向の Iterator) • Generator は値が必要になったときに初めて評価される – 並列で並べた時,Generator でない場合毎回全部ループして都度 全要素評価する – Generator の場合,一行読み込むたびに次の処理に移れる 30 なぜ Generator は省メモリなのか Qiita Night 〜PHP〜 メモリの使用量が要素数に比例しないので,無限長のリストを扱える
  14. 31 実際の処理を見てみよう Qiita Night 〜PHP〜 6, 2, 10, 9, 3,

    12 読み取りたいデータ <?php $data = read_data(); // 5 以上の整数に絞る $result = filter_1($data); // 偶数に絞る $result = filter_2($result); // 頭から 2 つだけとる $result = take($result, 2); foreach ($result as $item) { echo $item; } 擬似コード
  15. 32 Generator を使わない場合 Qiita Night 〜PHP〜 全部読み込み 全探索で絞り込み 全探索で絞り込み 早期リターン

    出力 <?php $data = read_data(); // 5 以上の整数に絞る $result = filter_1($data); // 偶数に絞る $result = filter_2($result); // 頭から 2 つだけとる $result = take($result, 2); foreach ($result as $item) { echo $item; }
  16. 33 Generator を使わない場合 Qiita Night 〜PHP〜 6 2 10 9

    3 12 read_data 1 2 3 4 5 6 filter_1 7 8 9 10 11 12 filter_2 13 14 15 16 take 17 18 foreach 19 20 データ 処理 黒の数字は読み取り順 処理が軸になる
  17. 34 Generator を使う場合 Qiita Night 〜PHP〜 1 要素目 2 要素目

    3 要素目 <?php $data = read_data(); // 5 以上の整数に絞る $result = filter_1($data); // 偶数に絞る $result = filter_2($result); // 頭から 2 つだけとる $result = take($result, 2); foreach ($result as $item) { echo $item; }
  18. 35 Generator を使う場合 Qiita Night 〜PHP〜 6 2 10 9

    3 12 read_data 1 6 8 filter_1 2 7 9 filter_2 3 10 take 4 11 foreach 5 12 データ 処理 黒の数字は読み取り順 値が軸になる
  19. • Generator を使わない場合 – 全てのデータを読み込んだ – メソッドは呼び出し時に実行された – 毎回全要素を参照した •

    take の早期リターンは例外 • Generator を使った場合 – 全てのデータは読み込まなかった – メソッドは呼び出し時に実行されなかった • 要素ごとに遅延実行された – 演算対象の要素のみ参照した 36 結果 Qiita Night 〜PHP〜 <?php $data = read_data(); // 5 以上の整数に絞る $result = filter_1($data); // 偶数に絞る $result = filter_2($result); // 頭から 2 つだけとる $result = take($result, 2); foreach ($result as $item) { echo $item; }