はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】デリゲート型の変数にメソッドを代入するときキャッシュすることでGC.Allocを防ぐ

はじめに

デリゲート型の変数にメソッドを代入しようとした際に、そのやり方だとGC Allocが発生しちゃうよとご指摘いただけたことがありました。

// イメージ
private void Update()
{
    hoge.OnCompleted += Fuga;
}

private void Fuga(){}

実際に発生するのか&どう対処すればいいのかを今回は考えていきたいと思います。

実験

public class Sample : MonoBehaviour
{
    private Action _onCompleted;
    private Action _onCompletedCache;

    private void Awake()
    {
        _onCompletedCache = OnCompleted;
    }

    private void Update()
    {
        // メソッドを直接突っ込むと毎回オブジェクトが生成されてしまう (GC Alloc)
        for(var i = 0; i < 100000; i++)
            _onCompleted = OnCompleted;
        
        // ラムダ式 (GC Allocは発生しない)
        for(var i = 0; i < 100000; i++)
            _onCompleted = () => Debug.Log("OnCompleted");
        
        // ただラムダ式もメンバ変数・ローカル変数・メンバ関数参照等によりGC Allocが発生する
        // 参考: https://neue.cc/2016/01/06_525.html
        for (var i = 0; i < 100000; i++)
            _onCompleted = () => OnCompleted();
        
        // キャッシュした場合 (GC Allocは発生しない)
        for(var i = 0; i < 100000; i++)
            _onCompleted = _onCompletedCache;
    }

    private void OnCompleted()
    {
        Debug.Log("OnCompleted");
    }
}
f:id:hanaaaaaachiru:20211207112618p:plainf:id:hanaaaaaachiru:20211207112621p:plain
GC Allocの発生の有無

考察

直感的にメソッドを直接代入した際にはGC Allocは発生しなさそうなイメージがありましたが、どうやらそれは間違いみたいです。

この場合、実際のところはデリゲートを生成して包んでます。そしてこのデリゲートはGCゴミになります。なります。全く見えないんですが地味にそうなってます。と、いうわけで、それを回避するには静的メソッドなら静的フィールドに静的コンストラクタででも事前に作ってキャッシュしておく、インスタンスメソッドの場合は、もし使うシーンがループの内側などの場合は外側で作っておくことで、生成は最小限に抑えられるでしょう。

neue cc - Unityでのボクシングの殺し方、或いはラムダ式における見えないnewの見極め方

対処法としてはキャッシュしておくのが良さそうですね。

またラムダ式に関して、GC Allocの発生条件は少し複雑になっているようです。
Unity での GC Alloc対策 ダイジェスト - Qiita