1
C#ゲームプログラミングは
ホントにメモリのことに無頓着でいいの?
KMC-ID:crys
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
2
自己紹介(C#er歴)
• B1で勉強してまる8年
• B1、B2でC#製ゲームを3本制作
• B2-B4でバイト:C#/WPF、VB6の秘伝のソース
• B4の研究、M1の学内ハッカソン、および
インターンのゲーム制作でUnityを使用
• 仕事はUnityとか
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
3
C#、C++よりばり便利
入学直後、新歓期の雑談にて:
「C++っていう闇の言語があるんだけど…
(黒魔術がヤバい的な話)」
「C++はメモリも自分で管理せなあかんくって。
C#はそのへん最後までいい感じにしてくれる」
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
当時の先輩
4
当時のcrysさん
「(メモリとか知らないけど)
C#すごい!!!!!!!」
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
5
C#で作った(学部時代)
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
妖精さんに貢ぐ
アクションパズル
妖精さんたちがお姫様を
守りながら進む
戦術シミュレーション
妖精さん等が導火線を
つなげて消していく
対戦アクションパズル
6
C#で作った(学部時代)
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
妖精さんに貢ぐ
アクションパズル
妖精さんたちがお姫様を
守りながら進む
戦術シミュレーション
妖精さん等が導火線を
つなげて消していく
対戦アクションパズル
見よう見まね
「再帰よくわからん」
コード書いてない
グラフィックと
プランニング
私が
作りました
7
ゲームがNFでトラブル
長時間プレイすると
(メニュー画面とパズル画面を行き来すると)
ゲームが重くなる
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
原因:メモリリーク。音声リソースの解放し忘れがあった
XNA(MonoGameの前身)では、Loadしたテクスチャや音声波形は
明示的にDispose関数を呼ばないとメモリから解放されない
→ゲームがどんどんメモリを使い潰していく!
私が
作りました
メモリ
メモリ
メモリ
8
ゲームがNFでトラブル
長時間プレイすると
(メニュー画面とパズル画面を行き来すると)
ゲームが重くなる
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
原因:メモリリーク。音声リソースの解放し忘れがあった
XNA(MonoGameの前身)では、Loadしたテクスチャや音声波形は
明示的にDispose関数を呼ばないとメモリから解放されない
→ゲームがどんどんメモリを使い潰していく!
私が
作りました
メモリ
メモリ
メモリ
「C++はメモリも自分で管理せなあかんくって。
C#はそのへん最後までいい感じにしてくれる」
9
~生みの王子~
C#で書ける!!!!!!ゲームエンジン
(Unityが流行り出した頃にはXNAは死んでた)
最高すぎていろいろ作った (院生時代)
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
センサデバイスのモニターテスト用
アプリケーション
コンソール向けゲーム全周画像ビューワ
Not printed Not printed
10
~時を超えた処理落ち~
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
シーンの動的読み込み・解放をしたら
めちゃくちゃ(>0.5sec)処理落ちした
原因:ガベージコレクション(メモリの自動解放)がクソ重い
Unityで使われているメモリ管理の仕組みが超☆前時代的で、
使っていたメモリを再利用可能にする処理が動くと
数フレーム単位でアプリの処理が止まる
メモリ
メモリ
メモリ
112018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
12
この講座の内容
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
メモリって何?
C#におけるメモリ管理
ガベージコレクションとうまく付き合う
君のコードに潜むガベージコレクションの元凶 Unity編
13
メモリって何?
を している人には真新しい話はありません
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
14
メインメモリ
本講座ではこれ以降、単に
「メモリ」と言った場合は
メインメモリを指します
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
ALU
レジスタ
(二次記憶)
15
メモリ
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
ALU
レジスタ
 データを格納するためのマスが並んでいる
 マスには番地が振られている
 番地情報を通してデータを読み書きする
中身を拡大
100
101
102
103
104
105
106
107
108
109
110
……
16
メモリの確保と解放
メモリのサイズは有限⇒同じ場所を使い回したい
ある番地に「まだ使うデータ」が置いてあるかどうかを
誰かが管理しておく必要がある
さもなくば…
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
100番地に置いておこう
100番地使おうとしたら
何かあったわウマー
100
101
99
100
101
99
17
メモリの確保と解放
メモリのサイズは有限⇒同じ場所を使い回したい
ある番地に「まだ使うデータ」が置いてあるかどうかを
誰かが管理しておく必要がある
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
100番地
確保
してます
超越的存在
100
101
99
18
メモリの管理方法
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
19
スタック
メモリ上の隣接した番地に積み上げ・積み降ろすように
データを配置・解放していくデータ管理方式
✓ 配置の効率がよい
✗ データがいつ使われなくなるかが明確でないと使えない
✗ 積めるサイズは有限
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
プログラムの関数呼び出しでは、より新しく作った変数ほど
より早く使わなくなるので、スタックで管理できる
20
ヒープ
データの大きさや、確保・解放の順序に依存することなく
データを管理できる方式
✓ スタックで管理できないデータを管理できる
✗ 使っている場所や大きさをすべて把握しておく必要がある
✗ 新たに空き領域を探す手間がある
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
C言語では malloc (C++ なら new 等) でメモリを確保したときは
ヒープにデータを置く
21
ヒープは管理が面倒
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
ヒープのどの領域が使われているかはOSが管理
確保と解放は利用者 (プログラム)の責任
利用者が解放し忘れるとOSに管理され続ける
メモリ
リーク
逆に二重に解放しても問題になる…
22
C#におけるメモリ管理
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
23
ガベージコレクション (GC)
C#(の実行環境)は
ヒープ上の使われなくなったメモリ領域 を自動的に解放する
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
まかせた
24
ガベージコレクション (GC)
C#(の実行環境)は
ヒープ上の使われなくなったメモリ領域 を自動的に解放する
ついでに空き領域を詰める (コンパクション)
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
25
GCと手動メモリ管理の比較
GC
メモリリークしないので、
管理が楽
解放できる領域の探索に
時間がかかる
手動メモリ管理
確保・解放する領域を
すべて指定しているので
GCよりも高速・低負荷
メモリリークに注意
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
26
…ん?
←こいつメモリリークしたじゃん
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
私が
作りました
∵ C#実行環境の外側にある機能を使用するときは、
OSのメモリを確保する必要がある!!
OSの管理しているメモリ
C#実行環境
音を鳴らす機能で
確保したメモリ
あの機能
使いたい
御意
音を鳴らす
機能
ブヒィィッ
27
C#でメモリリークしないためには…
• C#が管理していないメモリ(アンマネージドメモリ)を
利用するクラスが使わないか、ドキュメントを見る
• IDisposable が実装されているクラスのインスタンスで
確実に Dispose 関数を呼ぶ実装をする
• using 節が使えるなら使おう
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
using (var sr = new StreamReader(“example.txt”)) {
// do something
}
えー
28
Unityはメモリリークする?
Unityの機能だけを使用していてもメモリリークする(それっぽい状態になる)
アセットを解放しても参照されてるリソースが解放されない可能性
↓↓ 以下を読んで(丸投げ)
【Unity】アセットのUnloadとDestroyについて – テラシュールブログ
http://tsubakit1.hateblo.jp/entry/2016/10/02/232723
(あとは悪名高いアセットバンドルはアンロード忘れるとメモリリークするとか……)
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
29
ガベージコレクションとうまく付き合う
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
30
GC、実際重い
C#の処理系に依るが、環境によっては
メインの処理を止めて、
一気にインスタンスを破棄したりする
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
だと2フレームくらい
平気で持ってかれるかも…
31
ガベージ コレクションの条件
https://docs.microsoft.com/ja-jp/dotnet/standard/garbage-collection/fundamentals#conditions_for_a_garbage_collection
GCはいつ発生するの?
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
システムの物理メモリが少ない場合。
• OS からのメモリ不足通知またはホストによって示されたメモリ不足のいずれかによって検出されます。
オブジェクトによって使用されているメモリが、許容されるしきい値を超える場合。
• このしきい値は、プロセスの進行に合わせて絶えず調整されます。
GC.Collect メソッドが呼び出された場合。
• ほとんどの場合、ガベージ コレクターは継続して実行されるため、このメソッドを呼び出す必要は
ありません。このメソッドは、主に特別な状況やテストで使用されます。
GCは突然に
32
GC、実際重い (再掲)
C#の処理系に依るが、環境によっては
メインの処理を止めて、
一気にインスタンスを破棄したりする
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
弾幕をギリギリで避けてる時に2フレーム止まったらどうする???
332018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
34
GCを根源から断ち切るには?
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
ヒープを使わない…ことは難しい
• クラス型のインスタンスや配列はヒープに確保される
• フレームワーク側の機能がクラス型で実装されていることも多い
プレイアブルなシーンの間、GCを発生させないようにできないか?
• GCの発生条件は「メモリが、許容されるしきい値を超える場合」…
⇒ヒープに新たにメモリを確保しようとしなければ、
GCの発生は起きない
35
メモリ確保が発生しないプログラミング (1/2)
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
class GoodScene
{
private Bullet[] bullets;
public GoodScene()
{
bullets = new Bullet[256];
}
public void MainLoop()
{
bullets[index].position = ...
}
}
class BadScene
{
private List<Bullet> bullets;
public BadScene() {}
public void MainLoop()
{
var b = new Bullet();
bullets.Add(b)
b.position = ...
}
}
シーン開始時のロードで、必要な情報をすべてメモリに載せる
36
メモリ確保が発生しないプログラミング (2/2)
意図せぬメモリ確保がないかチェックする
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
Visual Studio の
診断ツール
Unity の
Profiler
37
君のコードに潜む
ガベージコレクションの元凶 Unity編
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
38
OnGUI
void OnGUI(){
GUI.Label(new Rect(0,0,32,32),”TEST”));
}
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
GUIクラスを使用して画面描画を行うと、
毎フレーム、ヒープへのメモリ確保が行われる
GUIは開発・デバッグ機能という認識。製品ではuGUIなどへ移行を
39
文字列演算
string はクラスなので、定義するとメモリに確保される (回避方法なし)
文字列の結合(+演算子)は、結合後の新しい文字列としてメモリに確保される
StringBuilder を使用すれば、メモリの確保を抑えることができる
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
string str = “点数は” + score.ToString() + “点です”;
StringBuilder sb = new StringBuilder();
void Update(){
sb.Append(“点数は”); sb.Append(score); sb.Append(“点です”);
string str = sb.ToString(); // メモリ確保はどうしても起きる
}
40
List
動的に配列のサイズを変えたくてListを使った?甘えんな!!
当然メモリの確保が行われる。
固定長配列使え固定長配列
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
// List<int> intList で定義している
intList.Add(3);
int[] intArray = new int[256];
41
LINQ
LINQめっちゃ使いたいけどメモリアロケーションあるねん…
for ループ回して配列なめてください…
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
using System.Linq;
...
// int[] intArray で定義している
intArray.Count(i => i == 3);
42
Coroutine (1/3)
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
IEnumerator TimerCoroutine()
{
// 300フレーム待つ
for(int i=0; i < 300; i++){
yield return new WaitForEndOfFrame();
}
// do something
}
StartCoroutine(TimerCoroutine());
43
Coroutine (2/3)
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
var waitForEndOfFrame = new WaitForEndOfFrame();
IEnumerator TimerCoroutine()
{
// 300フレーム待つ
for(int i=0; i < 300; i++){
yield return waitForEndOfFrame;
}
// do something
}
StartCoroutine(TimerCoroutine());
44
Coroutine (3/3)
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
var waitForEndOfFrame = new WaitForEndOfFrame();
IEnumerator TimerCoroutine()
{
// 300フレーム待つ
for(int i=0; i < 300; i++){
yield return waitForEndOfFrame;
}
// do something
}
StartCoroutine(TimerCoroutine());
そもそもコルーチンを
生成すると
メモリ確保される…
コルーチンなしで頑張る…?
45
などなど
詳しい情報は「Unity GC Alloc」みたいなのでググって
うにばな メモリ使用量の最適化・ヒープメモリ ほか – livedoor Blog
http://blog.livedoor.jp/akinow/archives/52474053.html
↑ただし、foreach で List を回すとメモリ確保する(クソ)仕様は
Unity 5.5 で修正済
neue cc - C#のGCゴミとUnity(5.5)のコンパイラアップデートによるListの
foreach問題解決について
http://neue.cc/2016/08/05_537.html
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
46
まとめ
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
47
まとめ
C#ゲームプログラミングでも
メモリのことを気にしてあげる必要がある
2018/3/19 春合宿2018講座;C#ゲームプログラムとメモリ;crys
アクションゲームのリアルタイム性を損ねないために
• ゲームシーン中にヒープからメモリ確保をしないこと
• 専用ツールでメモリの使用量を見るべし

C# ゲームプログラミングはホントにメモリのことに無頓着でいいの?