はじめに
デリゲート型の変数にメソッドを代入しようとした際に、そのやり方だと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"); } }
考察
直感的にメソッドを直接代入した際にはGC Alloc
は発生しなさそうなイメージがありましたが、どうやらそれは間違いみたいです。
この場合、実際のところはデリゲートを生成して包んでます。そしてこのデリゲートはGCゴミになります。なります。全く見えないんですが地味にそうなってます。と、いうわけで、それを回避するには静的メソッドなら静的フィールドに静的コンストラクタででも事前に作ってキャッシュしておく、インスタンスメソッドの場合は、もし使うシーンがループの内側などの場合は外側で作っておくことで、生成は最小限に抑えられるでしょう。
neue cc - Unityでのボクシングの殺し方、或いはラムダ式における見えないnewの見極め方
対処法としてはキャッシュしておくのが良さそうですね。
またラムダ式に関して、GC Alloc
の発生条件は少し複雑になっているようです。
Unity での GC Alloc対策 ダイジェスト - Qiita