問題
Unityで、次の2つのコードでGCAllocが発生するものしないものとで差がでたので調査する。コードはプロファイラーで見やすいように毎フレーム処理が走るようにしている。
1 毎フレームGCAllocが発生しないコード
void Update()
{
Method1(() => {});
}
void Method1(Action action)
{
}
2 毎フレームGCAllocが発生するコード
void Update()
{
Method1(Method2);
}
void Method1(Action action)
{
}
void Method2()
{
}
プロファイリングの結果
1 の結果
1の結果では GC Alloc が0だが、2の結果は104B GC Alloc が発生しているのがわかる。
ILを比較する
それぞれのコードでできるILを比較する。必要なところだけ抜粋
1 のコード
.class public auto ansi beforefieldinit GCAllocTest
extends [UnityEngine]UnityEngine.MonoBehaviour
{
.field private static class [mscorlib]System.Action '<>f__am$cache0'
...
// method line 2
.method private hidebysig
instance default void Update () cil managed
{
// Method begins at RVA 0x2058
// Code size 37 (0x25)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldsfld class [mscorlib]System.Action GCAllocTest::'<>f__am$cache0'
IL_0007: brtrue.s IL_001a
IL_0009: ldnull
IL_000a: ldftn void()
IL_0010: newobj instance void class [mscorlib]System.Action::'.ctor'(object, native int)
IL_0015: stsfld class [mscorlib]System.Action GCAllocTest::'<>f__am$cache0'
IL_001a: ldsfld class [mscorlib]System.Action GCAllocTest::'<>f__am$cache0'
IL_001f: call instance void class GCAllocTest::Method1(class [mscorlib]System.Action)
IL_0024: ret
} // end of method GCAllocTest::Update
2 のコード
// method line 2
.method private hidebysig
instance default void Update () cil managed
{
// Method begins at RVA 0x2058
// Code size 20 (0x14)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldarg.0
IL_0003: ldftn instance void()
IL_0009: newobj instance void class [mscorlib]System.Action::'.ctor'(object, native int)
IL_000e: call instance void class GCAllocTest::Method1(class [mscorlib]System.Action)
IL_0013: ret
} // end of method GCAllocTest::Update
2のコードから先に見ていく。2のコードの方は処理の中で毎回Actionをnewしている。そのため
GC Alloc を発生させている。
1のコードの方は lambda で定義されたコードが静的フィールドで保存されており、二回目以降はその保存されたコードが使われるようになっていた。
ということは最初の一回目は同じく1のコードも同じ量だけ GC Alloc が発生してたりするのかな。なにわともあれ原因がわかったのでよしとする。
環境
Unity5.6.3p3
macOS Sierra 10.12.6