弾幕シューティングの弾を素朴に1000発作ると、メモリはどれだけ使われるのか。同じ見た目の弾を1つのオブジェクトで共有すると、どこまで減るのか。Perl と Moo で実際に測りながら、Flyweight パターンにたどり着く全6回の連載です。
各話では、コードを書く前にまず Devel::Size で計測します。パターン名は最終回まで出てきません。作っていくうちに、気づけば Flyweight になっている流れです。
このページは目次を兼ねた案内板です。まず読むなら「まず読むなら」の2本から、パターンの用語から押さえたいなら「各回の要点」から選んでください。
まず読むなら
- 第1回: 弾を1000発撃ちたい!メモリの限界 — 素朴に作った Bullet が1発約600バイト、1万発で約6MB になる計測回。この連載の問いがすべてここにある。大量のオブジェクトを作るコードを抱えているなら、この1本だけでも測り方が持ち帰れる
- 第3回: BulletFactoryで弾を使い回そう — 同じ種類は作り直さず、あれば同じオブジェクトを返すキャッシュの回。
//=1行で書けるので、Flyweight に限らず使い回しの書き方として読める
読む前に
- 対象は Perl 入門者で、Moo の
hasとnewが読めれば足ります。前提シリーズは「Mooで覚えるオブジェクト指向プログラミング」です - コードは Perl v5.36 以降を前提にしています。メモリの計測には Devel::Size を使います
- 計測値は筆者の環境での一例です。環境によって変わるので、傾向として読んでください
各回の要点
第1回: 弾を1000発撃ちたい!メモリの限界
形状・色・サイズが全弾で同じなのに、弾ごとに持っている。1発あたり約600バイト、10000発で約5.8MB と測り、共有できる属性と弾ごとに違う属性を分けます。
第2回: 弾の種類と位置を分けて考えよう
形状・色・サイズを BulletType に切り出し、位置と速度だけを弾ごとのハッシュに残す。弾100発分は60.8KB から25.2KB になります。内部状態と外部状態という用語もここで出てきます。
第3回: BulletFactoryで弾を使い回そう
同じ種類の弾を何度頼んでも、同じ BulletType を返す BulletFactory を作ります。1000発の弾が参照する BulletType は5つで、弾の配列は約245.8KB です。
第4回: 外部状態を渡して弾幕を描こう
BulletType は位置を持たず、render が描画のたびに座標を受け取る。だから同じオブジェクトを複数の弾が共有できます。放射状と螺旋状に弾を動かします。
第5回: 完成!弾幕シューティングエンジン
BattleField クラスに、弾の生成・移動・描画をまとめます。放射状と螺旋の弾幕パターンで、68発を3種類の BulletType で動かします。
第6回: これがFlyweightパターンだ!
作ってきたものが Flyweight パターンだったと明かし、各クラスを役割に対応づけます。複製する Prototype との違いも表で比べます。
学習のポイント
このシリーズで扱う技術要素:
| 技術要素 | 内容 |
|---|---|
| 内部状態と外部状態 | 共有できる状態と、弾ごとに違う状態を分ける |
| FlyweightFactory | キャッシュで同じ種類のオブジェクトを使い回す |
//= 演算子 | 「なければ作る」を1行で書く |
| Flyweight パターン | GoF の構造パターンの一つ |
読み終えると
- 弾の配列が参照する BulletType は種類の数だけ、という作りが書ける
- 共有するオブジェクトと、渡し直す外部状態の線引きができる
- Devel::Size で、自分のコードのオブジェクトの大きさを測れる
関連シリーズ
共有ではなく複製でオブジェクトを増やす Prototype パターンのシリーズも、最終回で比べています:
