39
shared_ptr とととととととととととととととととと @DADA246

shared_ptrとゲームプログラミングでのメモリ管理

  • Upload
    dada246

  • View
    5.374

  • Download
    0

Embed Size (px)

Citation preview

Page 1: shared_ptrとゲームプログラミングでのメモリ管理

shared_ptrとゲームプログラミングでのメモリ管理@DADA246

Page 2: shared_ptrとゲームプログラミングでのメモリ管理

自己紹介• ゲームプログラマやってます• 家庭用からモバイル、業務用まで幅広く書いてきました

Page 3: shared_ptrとゲームプログラミングでのメモリ管理

アジェンダ• はじめに• メモリリーク• メモリ断片化• 処理速度• まとめ

Page 4: shared_ptrとゲームプログラミングでのメモリ管理

今回の話の対象• ハード: OSとゲームが動作するコンピュータ• ソフト: C++によるネイティブクライアント• ゲーム :アクションゲームなど、リアルタイム性が問われるジャンル• 開発規模 :中規模~大規模

Page 5: shared_ptrとゲームプログラミングでのメモリ管理

ゲームプログラムの特徴• フレームレートが安定している (60fps,30fps…)→スワップアウトが発生しない→実行時メモリサイズが物理メモリサイズに収まっている

Page 6: shared_ptrとゲームプログラミングでのメモリ管理

メモリ管理の種類• メモリ固定管理→ゲーム起動時に確保されたメモリのみ使用して、ゲーム中は OSからメモリ確保を行わない方式• メモリ動的管理→ゲーム動作中にメモリが必要になった時点で OSからメモリを確保して、メモリが不必要になったら OSにメモリを返却する方式→今回の話はコチラ

Page 7: shared_ptrとゲームプログラミングでのメモリ管理

なぜメモリの動的管理?• 近年の中規模~大規模ゲーム開発ではレベルデータ、グラフィックスリソース、サウンドなど、ゲームが扱うデータが増えている→ゲームをデータドリブンで動作させる必要がある→メモリを動的管理にすると、メモリによる制限が減るので、多彩なデータを動作させることができる

Page 8: shared_ptrとゲームプログラミングでのメモリ管理

試行錯誤• ゲームが多彩なデータを扱えると、ゲームデザイナやアーティストが思いついたアイデアを試行錯誤できる→メモリの動的管理を行うことで、「一時的にメモリを使用するが、ゲームが面白くなりそうなデータ」を試すことが出来る

Page 9: shared_ptrとゲームプログラミングでのメモリ管理

ゲームシーケンス• メモリの動的管理を行うことで、シーケンスごとにメモリ構成が大幅に異なるゲームを作ることが出来る• 例: FPSレベルプレイでは大量のゲームオブジェクト、 AI、ネットワークにメモリを割り当てるが、カットシーンではそれらの代わりにグラフィックスリソースにメモリを割り当てる

Page 10: shared_ptrとゲームプログラミングでのメモリ管理

メモリ動的管理の問題• メモリの動的管理にはメリットが多いが、問題点もあるメモリリークメモリ断片化処理速度→これらの問題点を解決する

Page 11: shared_ptrとゲームプログラミングでのメモリ管理

メモリリーク• std::freeや deleteを書き忘れると発生する• メモリ解放を他のプログラマに委ねるインターフェースでもメモリリークは発生しやすい

void Sample(){ void* pBuffer = std::malloc(1024); Player* pPlayer = new Player();}//メモリリーク発生

void Register() { m_pPlayer = new Player(); }

//呼び忘れるとメモリリークするvoid UnRegister() { delete m_pPlayer; }

Page 12: shared_ptrとゲームプログラミングでのメモリ管理

RAII• Resource Acquisition Is Initialization• オブジェクトの生存時間とリソースを結びつける• 基本的にこの設計を守ればメモリリークは起きない• 実装の一例として、 shared_ptrを利用する

Page 13: shared_ptrとゲームプログラミングでのメモリ管理

shared_ptr• newと deleteは使わずに shared_ptrを使う• 各種モジュールごとに shared_ptrで分離させておくと、生ポインタを扱わずに済む

class Player{private: std::shared_ptr<Collision> m_collision; std::shared_ptr<Sound> m_sound;}

Page 14: shared_ptrとゲームプログラミングでのメモリ管理

クラスの所有• クラスを直接所有して Initialize()するのではなく、 shared_ptrにしてコンストラクタで初期化する→C++の言語設計に従うことで、 RAIIを実装しやすくなるコンストラクタとデストラクタを活用する

Player::Player(){ m_life.Initialize();}

Player::Player(){ m_life = std::make_shared<Life>();}

Page 15: shared_ptrとゲームプログラミングでのメモリ管理

メモリリークは発生する?• バイナリデータ読み込みなど、 shared_ptrを使わずに

std::mallocを使う場合もある→RAIIを守るのが基本だが、 std::freeを書き忘れることがある→メモリリークが発生する→この場合はメモリリーク検出ツールで対応する

Page 16: shared_ptrとゲームプログラミングでのメモリ管理

カスタムアロケータ• あらかじめメモリの確保、開放時にデバッグ情報を保存できるカスタムアロケータを作り、 OSのアロケータを切り替えておく→デバッグ情報をログ出力することでメモリの状態を把握できる ゲーム

デバッグ情報の保存

アロケータ

void* _cdecl operator new (size_t size){ //カスタムアロケータ呼び出し}

Page 17: shared_ptrとゲームプログラミングでのメモリ管理

メモリリーク検出ツール• ゲームプログラムにおいて、メモリリークが問題になった時のメモリ状態を考える• メモリリークしているアドレスは開放されない→メモリの生存時間が長いアドレスはメモリリークの可能性が高い→アドレスに日付をつけるとメモリの生存時間を計算できる

struct Node{ int64_t address; int64_t size; int64_t date;}

Page 18: shared_ptrとゲームプログラミングでのメモリ管理

生存時間の長いアドレス• 常駐データなど、メモリ確保後に解放しないデータもある• メモリ確保時にコールスタックを保存して、ログ出力に含める→コールスタックが1回のみ記録されているものは常駐データ→同一コールスタックが複数回記録されているものはメモリリークの可能性が高い• コールスタックは RtlCaptureStackBackTraceなどで取得できる

Page 19: shared_ptrとゲームプログラミングでのメモリ管理

メモリリーク検出ツール• 同一のコールスタックから、生存時間の長いメモリを複数回確保している箇所はメモリリークしていると判断できる→エージングなどの自動テストに組み込むことで、メモリリークを自動的に検出できる

struct Node{ int64_t address; int64_t size; int64_t date; int64_t backTraceHash;}

//keyは backTraceHashstd::map<int64_t,std::vector<Node>> nodes

for(auto it = nodes.cbegin(); it != nodes.cend(); ++it){ if( 1 < it->second.size() ) { auto memoryLeak = it->second;//メモリリーク発見! }}

Page 20: shared_ptrとゲームプログラミングでのメモリ管理

アジェンダ• はじめに• メモリリーク• メモリ断片化• 処理速度• まとめ

Page 21: shared_ptrとゲームプログラミングでのメモリ管理

メモリ断片化• メモリ断片化とは何か→メモリの確保と解放を繰り返すことで、連続したメモリが確保出来なくなる→メモリの確保に失敗することになる

Page 22: shared_ptrとゲームプログラミングでのメモリ管理

連続したメモリ領域• ゲームから見て連続したメモリ領域はMMUの有無で変わるMMUが有る場合は仮想アドレス空間MMUが無い場合は物理アドレス空間• ハードウェア構成によって断片化のしやすさが変わる→メモリ戦略が変わってくる

Page 23: shared_ptrとゲームプログラミングでのメモリ管理

MMU 非搭載の場合• 物理アドレス空間の断片化に気をつける必要がある• 低コストの組み込みマシンの場合が多いので、処理性能を考えると、メモリ固定管理の方が適している場合もある例:物理メモリ 16MByte 程度の組み込みハードウェアなど Physical memory

Page 24: shared_ptrとゲームプログラミングでのメモリ管理

MMUが搭載されている場合• MMUを搭載していて、仮想アドレス空間と物理メモリ量に差がある場合→仮想メモリ空間の大きさに頼る• フレームレートを維持するというゲームの特性上、ソフトウェアの規模が物理メモリ量に収まるサイズになることを利用する

Virtual memory Physical memory

Page 25: shared_ptrとゲームプログラミングでのメモリ管理

断片化対策• 例 :64bit Windows8.1→プロセスあたりの仮想メモリ空間は 128TByte→ページ単位のブロックアロケータを使い、仮想メモリ断片化は気にしない• Windowsの low fragmentation heapが参考になる• 断片化よりもアロケータの適切なページ管理が重要

Page 26: shared_ptrとゲームプログラミングでのメモリ管理

断片化対策• MMUを搭載していて、仮想アドレス空間と実メモリに差が少ない場合 (32bitコンピュータに GByte 単位の実メモリを載せている場合など )→仮想アドレス空間の断片化に気をつける必要がある• 例: 32bit Windows XPで、メモリ使用量 2GByteのゲーム

Virtual memory Physical memory

Page 27: shared_ptrとゲームプログラミングでのメモリ管理

断片化対策• 仮想アドレス空間と実メモリに差が少ないマシンでメモリ断片化を防ぐには?→生存時間の長いメモリを減らす→生存時間の長いメモリと、生存時間の短いメモリのアドレスを離す

Virtual memory Physical memory

Page 28: shared_ptrとゲームプログラミングでのメモリ管理

生存時間の長いメモリを減らす• shared_ptrの相互参照を減らして、一つの shared_ptrを削除すると、所有している shared_ptrが削除されるようにしておく→シンプルに大量のモジュールを作り直すことが出来るメモリの生存時間が短くなるので、ゲームが長時間駆動に耐えやすくなる• デバッグ機能など依存関係が増えやすいものは weak_ptrにする shared_ptr

shared_ptr shared_ptr

Page 29: shared_ptrとゲームプログラミングでのメモリ管理

生存時間の長いメモリを減らす• ゲームシーケンスの切り替え時にモジュールを作りなおすようにする• シーケンス間のデータ移行のためのモジュールを作ることもある

void LevelClear(){ std::shared_ptr<Status> status = m_level->GetStatus(); m_level = std::make_shared<Level>(status);}

Page 30: shared_ptrとゲームプログラミングでのメモリ管理

生存時間の長いメモリを減らす• シングルトンなど、グローバルインスタンスは極力使わない→デバッグモジュールなど、グローバルインスタンスになりやすいものもある• グローバルインスタンスはゲーム開始直後など、他のメモリアロケーションが走る前にメモリを確保して、断片化された領域を作らないようにする

グローバルインスタンス→Virtual Memory

空き領域→

Page 31: shared_ptrとゲームプログラミングでのメモリ管理

断片化対策• 生存時間の長いメモリと、生存時間の短いメモリのアドレスを離す→アロケータを複数用意するDouble Ended Stack allocatorを使うこともある

Virtual Memory

Page 32: shared_ptrとゲームプログラミングでのメモリ管理

VRAM• 物理アドレス空間の断片化に気をつける必要がある(GPUによっては仮想アドレスに対応している場合もある )

• 断片化しやすいが、いざとなったらメモリコンパクションを実装することもできる→リソースをハンドルで管理していることが多いためVRAM

Page 33: shared_ptrとゲームプログラミングでのメモリ管理

アジェンダ• はじめに• メモリリーク• メモリ断片化• 処理速度• まとめ

Page 34: shared_ptrとゲームプログラミングでのメモリ管理

処理速度• ある程度メモリ確保を高速化した上で、その処理速度に見合ったゲームにする→Win32の HeapAllocなど、 Kernel-landで実行されるアロケータではなく、 User-landで実行される独自のカスタムアロケータを用意する• メモリ動的管理ではメモリ確保が頻繁に実行されるので、メモリ固定管理よりも処理が重いと言われることはあるが、これは開発環境の良さとのバランスの問題

Page 35: shared_ptrとゲームプログラミングでのメモリ管理

メモリ動的管理の問題点と解決方法• メモリリーク → RAII(shared_ptr)とメモリリーク検出ツールで解決• メモリ断片化 → 仮想アドレス空間の広さを活用して解決• 処理速度 → アロケータを高速化しつつ、処理速度に見合ったゲームにすることで解決

Page 36: shared_ptrとゲームプログラミングでのメモリ管理

メモリが不足したら?• メモリが不足しないようにゲームを作るのが基本→自動テストやエージングを繰り返して、メモリ使用量を把握する• メモリリークとメモリ断片化を解決してもメモリが不足する場合は、ゲームに対してデータが大きすぎるので、データを修正する

auto player = std::make_shared<Player>();player->Update(); //playerは必ず存在する

Page 37: shared_ptrとゲームプログラミングでのメモリ管理

まとめ• メモリの動的管理が可能になったことで、ゲーム上で多彩なデータを動作させることが可能になった→ゲームデザイナやアーティストのクリエイティビティを引き出して、もっとゲームを面白くしよう!

Page 38: shared_ptrとゲームプログラミングでのメモリ管理

Questions?

Page 39: shared_ptrとゲームプログラミングでのメモリ管理

ご静聴ありがとうございました