メモリ断片化とその対策
ゲームプログラム上で何も考えずにメモリの確保や解放を繰り返しているとメモリ断片化がおこります。
メモリ断片化は最悪のケースでゲームを停止させてしまう原因となってしまいます。
今回はメモリ断片化についてと、ゲームプログラムでよく使う断片化を防ぐ方法についてお話します。
メモリ断片化とは
メモリ断片化とはメモリの確保や解放を繰り返した結果、連続した空きメモリ領域が少なくなる現象です。英語ではメモリフラグメンテーションとも呼びます。この記事では以降、断片化と略して表記します。
断片化のおこる詳しいメカニズムについては、こちらのサイトをご覧ください。図つきで分かりやすいです。
フラグメンテーション | 学校では教えてくれないこと | [技術コラム集]組込みの門 | ユークエスト株式会社
https://www.uquest.co.jp/embedded/learning/lecture17.html
メモリ断片化によって発生しうる事象
断片化は次のような事象の原因となりえます。
- CPUキャッシュ効率が悪くなりパフォーマンスが低下する、
- 本来必要なメモリの確保に失敗しアプリケーションが強制終了する。
前者は(程度にもよりますが)許容できたとしても、後者はプレイヤーのゲームプレイ体験に大きく影響してしまいます。
強制終了しなければ対策しなくていいんじゃないかという考え方
時々「長時間のテストプレイで問題がでなければ断片化対策は不要だ」という考え方のプログラマさんと出会います。対策も対応コストはゼロではないですしゲーム開発にかけられるコストも無限ではありませんからそれも1つの考え方だと自分も考えます。
ただこうした考えを採用した場合、次のリスクと向き合う必要がでてきます。
- 十分なテストプレイ時間を定義できないゲームでは100%強制終了がおきないと断言できない。
- 断片化による不具合が発生するのを確認から対策する場合、余計に対策コストがかかる可能性がある。
- ↑の状況が開発終盤に起きた場合、対策に時間がかかり納期に影響が出る。
趣味で製作しているフリーソフトのゲームであればそこまでを気を張る必要はないですからこの考え方を採用しても問題ありません。しかし、商用のゲームであればそれは売り物です。開発に責任を持つという意味で、この考え方を採用した場合の懸念事項と向き合っておきましょう。
1番についての補足
例えば対戦格闘ゲームで1試合終わる毎にメモリがクリアされることが保証されているのであれば『十分な時間』が定義できそうです。ただし、1試合に対して制限時間がない場合は『十分な時間』の定義が難しくなります。
メモリ断片化の対策方法の紹介
いくつかある断片化対策の方法を紹介していきます。
対策方法1. メモリ確保したものと逆順にメモリ解放することを徹底する
A,B、Cという順番にメモリ確保したのであればC,B,Aの順番でメモリ解放すれば断片化はおこりません。これを徹底すれば断片化はおきません。極めてシンプルな方法ですが、これを全てのメモリブロックに対して行うのはなかなか大変です。
対策方法2. 最大必要量で事前に確保したメモリブロックを使い回す
例えば次のようなケースがあったとします。
- 3Dモデルアニメーションの計算にワークメモリが必要。
- ワークメモリに必要な容量はアニメーションのデータによって異なる。
- 3Dモデルアニメーションは同時に1つしか再生しないものとする。(※ワークメモリのメモリブロックは同時に1つのみ必要であるということ)
素直に実装するとアニメーションを再生するたびに新規にワークメモリ用のメモリを確保し、アニメーションが終了したらメモリを解放することになるでしょう。しかしそうすると断片化が発生してしまう可能性があります。
このようなケースで、もし一番容量を使うアニメーションのデータが分かっているのであれば、そのデータが必要とするワークメモリサイズのメモリブロックを事前に確保しておきます。先に確保しておいたそのメモリブロックをワークメモリに割り当てて使用すれば不要なメモリ確保・解放がおきず、結果断片化を防げます。
対策方法3. メモリ解放を一気に行う
断片化はメモリ解放をメモリ確保の合間に行うと発生します。ということは、メモリ確保の合間にメモリ解放をしないようにすれば断片化を防ぐことができます。
もちろんメモリ解放を一切せずメモリ確保をし続けるといずれメモリは枯渇してしまいます。ですので、この方法を採用した場合はメモリ確保が無尽蔵に増えないようにする対策が必須となります。
例えばキャラクタの出現や消失が何回も起こるようなゲームがあったとして、何も考えずに実装するとキャラクタの出現や消失のたびにメモリ確保とメモリ解放がおきてしまいます。このようなケースでは以下のようにすることでメモリ確保の回数を抑えることができます。
- キャラクタは消滅する際、使用していたメモリブロックを空きメモリブロックに登録。
- 新たに出現するキャラクタは空きメモリブロックがあればそちらを使う。もし空きメモリブロックがなければ新たにメモリブロックを確保する。
そして、ステージが切り替わる画面暗転タイミングなどで今まで確保してきたキャラクタのメモリブロックを一気に解放すれば断片化はおきずにメモリ解放ができます。
対策方法4. 確保するメモリブロックサイズを統一する
断片化は確保するメモリブロックのサイズがバラバラなときに起こります。ということは、確保するメモリブロックのサイズを統一できれば断片化は起きなくなります。
例えば、Aは128バイト、Bは512バイト、Cは64バイト、それぞれ必要だったとします。この場合、AやCがメモリ確保するときも最大サイズである512バイト確保するようにすればブロックサイズが統一されます。
無駄なメモリ空間が発生するというデメリットはありますが、それと引き替えに断片化を防ぐことができます。
対策方法5. メモリブロックの寿命に合わせてメモリ確保処理を変える
とても寿命が短いメモリブロックの確保と解放は断片化の原因になりやすいです。マルチスレッドなゲームはもちろんですがシングルスレッドなゲームでもわりと起こります。
一時的に確保するようなメモリブロックはそれ専用のメモリ空間から確保すると断片化が発生しづらくなります。
Tips
システムによってはメモリを確保する際にヒープの前方からとるか後方からとるか選べる機能があります。そのようなシステムでは、寿命が長いものは前方から、一時的なものは後方からメモリ確保することでこの方法を実現できます。
対策方法6. 断片化を解消する仕組みを導入する
断片化がおきてもそれを解消してくれる仕組みがあれば断片化を心配する必要はなくなるはずです。この断片化を解消する処理をメモリコンパクションといいます。
メモリコンパクションは断片化した使用中のメモリブロックをヒープ上で移動させて断片化をなくします。
メモリコンパクションを入れれば全てを解決できそうに聞こえるかもしれません。しかし、メモリコンパクションは特殊なメモリ管理が必要とされますし、その処理負荷も決して軽いとはいえません。そのため、もしメモリコンパクションを採用するとしても採用するモジュールの範囲や容量・ブロック数などを最適化する必要があります。
対策方法は組み合わせて使う
ここで紹介したものどれか1つを採用する、というのではなく組み合わせて使うことが重要です。
ゲームプログラム上のメモリ確保・解放の性質はモジュール・ライブラリによってそれぞれ異なります。ですので、それぞれにあったものを採用して使うとよいでしょう。
例えば、ゲーム起動時に解消してそれからずっと破棄しないようなものは1番を。ある程度メモリブロックサイズが小さく大量に必要とされるようなものは4番を。3Dモデルやモーションなどのバイナリリソースは6番を。といった感じです。
どこに何を使えばいいのかというのに正解はありませんので、それぞれのアプリケ-ションにあった対策をとってください。
仮想アドレス対応OSで断片化対策は不要?
OSによっては仮想アドレスという機能をサポートしています。PC環境では昔からあったもので最近では一部のコンシューマゲーム機でも採用するものが出てきているようです。
仮想アドレスをサポートするOSでは適当にメモリ解放やメモリ確保を繰り返してもある程度断片化されないようにできているようです。ただし、それも完全ではないため絶対安心とは言い切れません。
Tips
仮想アドレスについて詳しく知りたい方は「仮想アドレス ページング」といったキーワードでインターネット検索してみてください。
また、仮想アドレスを使うにはOSの用意するメモリ確保・メモリ解放を実行する必要がありOSによってはオーバーヘッドがかかる可能性があり、決してノーコストとは言えません。
絶対安心とはなりませんが仮想アドレス対応OSでオーバーヘッドなど気にならないような環境では断片化対策をOSに任せるというのも対策の1つと考えてよいでしょう。
Tips
仮想アドレスを採用していない従来のコンシューマーゲーム機では、オーバーヘッドを最低限にするため起動時にメモリを一括確保してアプリケーション側でヒープを作成しメモリ管理するのが定番のようです。(※注:少なくとも筆者はそう認識しています)
おわり
以上、メモリ断片化とその対策のお話でした。
ゲーム開発においてチーム内のプログラマ全員が断片化を気をつけながらコーディングできればそれにこしたことはありません。ですが、全員にそれを求めるのは困難な場合がほとんどですし、そもそも断片化を気にせずコーディングできたほうがクリエイティブなことに集中できます。
ですので、チーム内で断片化を気にしなくてよいシステムを用意し、そのシステムの上で多くのプログラマがコーデイングできるようにすることが大事だと筆者は考えます。
リンク:ゲームプログラマの小話-目次