Gotanda.unity #10 メモリと闘う者達 〜GC編〜
by
Nozomi Tanaka
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
メモリと闘う者達 〜GC編〜 Nozomi Tanaka / @nozomin770 Gotanda.unity #10 2019/1/23
Slide 2
Slide 2 text
Tanaka Nozomi(タナカ ノゾミ) ・ワンダープラネット株式会社 - グローバルスタジオ所属 ・Unity使い4年半、Cocos2d-xもはじめました ・Ruby、PHPなどを使ったサーバーエンジニアでもあります ・プロジェクトマネジメントとインフラの修行もはじめました ・Twitter:@nozomin770
Slide 3
Slide 3 text
アジェンダ ・1分でわかるメモリの仕組み ・C#とGC ・GCで死ぬ(フリーズする)ゲームを救えないのか ・ボックス化、ラムダ式、クラス型と構造体、愉快な仲間たち ・まとめ
Slide 4
Slide 4 text
これは、GCと戦う 熱き勇者達の物語である
Slide 5
Slide 5 text
1分で分かるメモリ管理 メモリには2種類の保存場所があります 「スタック」という場所と 「ヒープ」という場所の2つです
Slide 6
Slide 6 text
スタックのルール 先入れ、先出し、隙間なし 隙間なく積み上げるため効率がいい
Slide 7
Slide 7 text
ヒープのルール 空いているところにうまいこと入れる 隙間あり、効率はよくない
Slide 8
Slide 8 text
全部スタックだと効率がよいのでは? ・スタックが使えるときは限られている ・プリミティブ型はスタックを使えると思えばOK (object、stringは除く) ・その他のほとんどの場合はヒープのメモリを使うことになる
Slide 9
Slide 9 text
ヒープが悪い? そういうわけじゃない
Slide 10
Slide 10 text
C#のメモリとの付き合い方 ・C#はガベージコレクション(以下GC)という仕組みで メモリの片付けを自動管理してくれている ・GCが走るとき、UnityではGC終了まで処理が停止してしまう (俗に言う処理落ち)
Slide 11
Slide 11 text
GCの発生条件をつかもう ・OSの物理メモリが足りない場合 ・利用可能なヒープメモリの使用量が一定のしきい値を超えた場合 なおしきい値は常に変動する(!?) ・GC.Collectメソッドが呼び出された場合
Slide 12
Slide 12 text
GCが走る世界線を 回避できないのか・・・?
Slide 13
Slide 13 text
GCの動作原理を支配する ・C#のGCは「到達可能性」を見て回収可能な変数を見ます ・「到達可能性」の変動条件 →ヒープ領域へのメモリ確保(アロケート)が原因 →これは変数宣言時に行われる
Slide 14
Slide 14 text
到達可能性の変動を抑える ・ヒープ領域への確保が行われる条件 - ボックス化(ボクシング) - クラス型のオブジェクト生成 ・上記を回避するようにコードを書くことでGCが走らない世界線にたどり 着けるはずだ・・・
Slide 15
Slide 15 text
どうやって・・・
Slide 16
Slide 16 text
ボックス化とは ・値型をobject型(参照型)として扱うとき、スタック上からヒープ上に値を コピーする処理を「ボックス化」(boxing)と呼びます ・値型を使いながらもヒープ領域を使ってしまうため、(個々は僅かだが) 無駄に重くなる ・ヒープ領域へのアロケートが行われてしまう(GCを誘発する可能性が 生まれる)
Slide 17
Slide 17 text
ボックス化を(適度に)避けたい - メソッド引数がobject型になってないか気をつける - Equalsは生で使うとobject引数が多い… - 比較対象クラスへのIEquatableインターフェース実装がお すすめ - 一部のUnityのクラスはDictionaryでボックス化が起きる - structでないクラス - またはIEquatableを実装していないクラス - UnityEngine.Objectを継承してるクラスに多い
Slide 18
Slide 18 text
とはいってもやりすぎは辛い ・ボックス化が起きる条件は値型の取り扱いのミスに多い ・int型をobject型に変換 ・ジェネリックは使ってOK ・(Listにintを入れてもboxingされない) ・GCが起きていい時はボックス化に気にする必要はない (と思ってます・・・)
Slide 19
Slide 19 text
クラス型オブジェクトの生成を回避 ・ラムダ式の展開式に気をつける ・メソッドをラムダ式に入れると展開時にデリゲートをnewする ・ラムダ式にメソッドを食わせる場合は大体ゴミが出る ・値型以外を使っても勿論ゴミは出る ・コアロジックなどループする箇所では注意 ・ラムダ式にメソッドを入れないのは現実的ではない
Slide 20
Slide 20 text
その他の回避策 ・事前確保によりゲーム中のアロケートを避ける - 確保時に足りないから到達可能性を見る - 確保するから到達可能性が変わってしまう - GC発生してほしくない時は確保しなきゃいいじゃん理論 - あんまり現実的ではないかも。インゲーム全体を管理可能な 規模ならアリ ・データクラスはクラスでなく構造体で作る、とか。
Slide 21
Slide 21 text
クラス型にする? それとも参照型? ・複製を行わないデータは構造体で作ると良い - 値型は代入時に複製が必要で、重くなり得る ・「クラスの継承」や「仮想メソッド」など多態性を使いたい場合はクラスで 作ると良い - というかクラスでしかできない
Slide 22
Slide 22 text
その他、これはどうなの編 - foreachはだめ? - IL2CPPがfor文に展開してくれる - for文でボックス化が発生しないコードならOK - UniRxはだめ? - ふつーに使う分には問題ないと思います(内部コードは対策済 み) - オペレータの引数に入れるラムダ式がボックス化を誘発する コードの場合、展開時にボックス化すると思います
Slide 23
Slide 23 text
その他、これはどうなの編 - LINQはだめ? - 完全にものによる・・・ - ラムダ式がある場合はラムダ式の展開式に注意 - 引数がある場合、値型で送るかジェネリック型で送れるなら ボックス化はしないはず - LINQの内部実装でボックス化してたら重いLINQメソッドなの で諦める
Slide 24
Slide 24 text
まとめ - メモリの管理場所「スタック」と「ヒープ」を意識する - GCの発生条件を意識する - 「到達可能性」を変化させないことで間接的にGC発生率を減ら せる - 「new」「ボックス化」を抑える - ラムダ式は展開時にGCを誘発するケースに注意
Slide 25
Slide 25 text
参考にさせていただいたHP - https://www.slideshare.net/KMC_JP/c-91154309 - http://neue.cc/2016/01/06_525.html - https://www.scriptlife.jp/contents/programming/2017 /05/14/unite2017-performance/
Slide 26
Slide 26 text
ご清聴 ありがとうございました