Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Gotanda.unity #10 メモリと闘う者達 〜GC編〜

Nozomi Tanaka
January 23, 2019
2.6k

Gotanda.unity #10 メモリと闘う者達 〜GC編〜

21Pに誤記
誤:クラス型にする? それとも参照型?
正:クラス型にする? それとも値型?

誤記、どっちも同じ・・・

Nozomi Tanaka

January 23, 2019
Tweet

Transcript

  1. メモリと闘う者達
    〜GC編〜
    Nozomi Tanaka / @nozomin770
    Gotanda.unity #10 2019/1/23

    View Slide

  2. Tanaka Nozomi(タナカ ノゾミ)
    ・ワンダープラネット株式会社 - グローバルスタジオ所属
    ・Unity使い4年半、Cocos2d-xもはじめました
    ・Ruby、PHPなどを使ったサーバーエンジニアでもあります
    ・プロジェクトマネジメントとインフラの修行もはじめました
    ・Twitter:@nozomin770

    View Slide

  3. アジェンダ
    ・1分でわかるメモリの仕組み
    ・C#とGC
    ・GCで死ぬ(フリーズする)ゲームを救えないのか
    ・ボックス化、ラムダ式、クラス型と構造体、愉快な仲間たち
    ・まとめ

    View Slide

  4. これは、GCと戦う
    熱き勇者達の物語である

    View Slide

  5. 1分で分かるメモリ管理
    メモリには2種類の保存場所があります
    「スタック」という場所と
    「ヒープ」という場所の2つです

    View Slide

  6. スタックのルール
    先入れ、先出し、隙間なし
    隙間なく積み上げるため効率がいい

    View Slide

  7. ヒープのルール
    空いているところにうまいこと入れる
    隙間あり、効率はよくない

    View Slide

  8. 全部スタックだと効率がよいのでは?
    ・スタックが使えるときは限られている
    ・プリミティブ型はスタックを使えると思えばOK
    (object、stringは除く)
    ・その他のほとんどの場合はヒープのメモリを使うことになる

    View Slide

  9. ヒープが悪い?
    そういうわけじゃない

    View Slide

  10. C#のメモリとの付き合い方
    ・C#はガベージコレクション(以下GC)という仕組みで
    メモリの片付けを自動管理してくれている
    ・GCが走るとき、UnityではGC終了まで処理が停止してしまう
    (俗に言う処理落ち)

    View Slide

  11. GCの発生条件をつかもう
    ・OSの物理メモリが足りない場合
    ・利用可能なヒープメモリの使用量が一定のしきい値を超えた場合
     なおしきい値は常に変動する(!?)
    ・GC.Collectメソッドが呼び出された場合

    View Slide

  12. GCが走る世界線を
    回避できないのか・・・?

    View Slide

  13. GCの動作原理を支配する
    ・C#のGCは「到達可能性」を見て回収可能な変数を見ます
    ・「到達可能性」の変動条件
    →ヒープ領域へのメモリ確保(アロケート)が原因
    →これは変数宣言時に行われる

    View Slide

  14. 到達可能性の変動を抑える
    ・ヒープ領域への確保が行われる条件
    - ボックス化(ボクシング)
    - クラス型のオブジェクト生成
    ・上記を回避するようにコードを書くことでGCが走らない世界線にたどり
    着けるはずだ・・・

    View Slide

  15. どうやって・・・

    View Slide

  16. ボックス化とは
    ・値型をobject型(参照型)として扱うとき、スタック上からヒープ上に値を
    コピーする処理を「ボックス化」(boxing)と呼びます
    ・値型を使いながらもヒープ領域を使ってしまうため、(個々は僅かだが)
    無駄に重くなる
    ・ヒープ領域へのアロケートが行われてしまう(GCを誘発する可能性が
    生まれる)

    View Slide

  17. ボックス化を(適度に)避けたい
    - メソッド引数がobject型になってないか気をつける
    - Equalsは生で使うとobject引数が多い…
    - 比較対象クラスへのIEquatableインターフェース実装がお
    すすめ
    - 一部のUnityのクラスはDictionaryでボックス化が起きる
    - structでないクラス
    - またはIEquatableを実装していないクラス
    - UnityEngine.Objectを継承してるクラスに多い

    View Slide

  18. とはいってもやりすぎは辛い
    ・ボックス化が起きる条件は値型の取り扱いのミスに多い
    ・int型をobject型に変換
    ・ジェネリックは使ってOK
    ・(Listにintを入れてもboxingされない)
    ・GCが起きていい時はボックス化に気にする必要はない
    (と思ってます・・・)

    View Slide

  19. クラス型オブジェクトの生成を回避
    ・ラムダ式の展開式に気をつける
    ・メソッドをラムダ式に入れると展開時にデリゲートをnewする
    ・ラムダ式にメソッドを食わせる場合は大体ゴミが出る
    ・値型以外を使っても勿論ゴミは出る
    ・コアロジックなどループする箇所では注意
    ・ラムダ式にメソッドを入れないのは現実的ではない

    View Slide

  20. その他の回避策
    ・事前確保によりゲーム中のアロケートを避ける
    - 確保時に足りないから到達可能性を見る
    - 確保するから到達可能性が変わってしまう
    - GC発生してほしくない時は確保しなきゃいいじゃん理論
    - あんまり現実的ではないかも。インゲーム全体を管理可能な
    規模ならアリ
    ・データクラスはクラスでなく構造体で作る、とか。

    View Slide

  21. クラス型にする? それとも参照型?
    ・複製を行わないデータは構造体で作ると良い
    - 値型は代入時に複製が必要で、重くなり得る
    ・「クラスの継承」や「仮想メソッド」など多態性を使いたい場合はクラスで
    作ると良い
    - というかクラスでしかできない

    View Slide

  22. その他、これはどうなの編
    - foreachはだめ?
    - IL2CPPがfor文に展開してくれる
    - for文でボックス化が発生しないコードならOK
    - UniRxはだめ?
    - ふつーに使う分には問題ないと思います(内部コードは対策済
    み)
    - オペレータの引数に入れるラムダ式がボックス化を誘発する
    コードの場合、展開時にボックス化すると思います

    View Slide

  23. その他、これはどうなの編
    - LINQはだめ?
    - 完全にものによる・・・
    - ラムダ式がある場合はラムダ式の展開式に注意
    - 引数がある場合、値型で送るかジェネリック型で送れるなら
    ボックス化はしないはず
    - LINQの内部実装でボックス化してたら重いLINQメソッドなの
    で諦める

    View Slide

  24. まとめ
    - メモリの管理場所「スタック」と「ヒープ」を意識する
    - GCの発生条件を意識する
    - 「到達可能性」を変化させないことで間接的にGC発生率を減ら
    せる
    - 「new」「ボックス化」を抑える
    - ラムダ式は展開時にGCを誘発するケースに注意

    View Slide

  25. 参考にさせていただいた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/

    View Slide

  26. ご清聴
    ありがとうございました

    View Slide