Unity のコルーチン待機での yield retun 0 と yield retun null の違い

yield retun 0 と yield retun null でGCが発生するから yield retun null にしろ、という記事などがありますが、なぜGCAllocが発生してしまうのかを見てみましょう。

どのような展開がされるのかを確認するために使用するのは、SharpLabです。

yield return null

using System;
using System.Collections;
using System.Collections.Generic;

public class C {
    public IEnumerator M()
    {
        yield return null;
    }
}

yield retun 0

using System;
using System.Collections;
using System.Collections.Generic;

public class C {
    public IEnumerator M()
    {
        yield return 0;
    }
}

それぞれを展開すると、以下のようになります。

yield return null

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
public class C
{
    [CompilerGenerated]
    private sealed class <M>d__0 : IEnumerator<object>, IDisposable, IEnumerator
    {
        private int <>1__state;

        private object <>2__current; // 値はObject型

        object IEnumerator<object>.Current
        {
            [DebuggerHidden]
            get
            {
                return <>2__current;
            }
        }

        object IEnumerator.Current
        {
            [DebuggerHidden]
            get
            {
                return <>2__current;
            }
        }

        [DebuggerHidden]
        public <M>d__0(int <>1__state)
        {
            this.<>1__state = <>1__state;
        }

        [DebuggerHidden]
        void IDisposable.Dispose()
        {
        }

        private bool MoveNext()
        {
            int num = <>1__state;
            if (num != 0)
            {
                if (num != 1)
                {
                    return false;
                }
                <>1__state = -1;
                return false;
            }
            <>1__state = -1;
            <>2__current = null; // ここが違う
            <>1__state = 1;
            return true;
        }

        bool IEnumerator.MoveNext()
        {
            //ILSpy generated this explicit interface implementation from .override directive in MoveNext
            return this.MoveNext();
        }

        [DebuggerHidden]
        void IEnumerator.Reset()
        {
            throw new NotSupportedException();
        }
    }

    [IteratorStateMachine(typeof(<M>d__0))]
    public IEnumerator M()
    {
        return new <M>d__0(0);
    }
}

yield return 0

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
public class C
{
    [CompilerGenerated]
    private sealed class <M>d__0 : IEnumerator<object>, IDisposable, IEnumerator
    {
        private int <>1__state;

        private object <>2__current; // 値はObject型

        object IEnumerator<object>.Current
        {
            [DebuggerHidden]
            get
            {
                return <>2__current;
            }
        }

        object IEnumerator.Current
        {
            [DebuggerHidden]
            get
            {
                return <>2__current;
            }
        }

        [DebuggerHidden]
        public <M>d__0(int <>1__state)
        {
            this.<>1__state = <>1__state;
        }

        [DebuggerHidden]
        void IDisposable.Dispose()
        {
        }

        private bool MoveNext()
        {
            int num = <>1__state;
            if (num != 0)
            {
                if (num != 1)
                {
                    return false;
                }
                <>1__state = -1;
                return false;
            }
            <>1__state = -1;
            <>2__current = 0; // ここが違う
            <>1__state = 1;
            return true;
        }

        bool IEnumerator.MoveNext()
        {
            //ILSpy generated this explicit interface implementation from .override directive in MoveNext
            return this.MoveNext();
        }

        [DebuggerHidden]
        void IEnumerator.Reset()
        {
            throw new NotSupportedException();
        }
    }

    [IteratorStateMachine(typeof(<M>d__0))]
    public IEnumerator M()
    {
        return new <M>d__0(0);
    }
}

yield return 0 は boxingとunboxingが働いてしまう

上記のURLの「ボックス化変換の図」説明にあるように、スタックに配置した int 0 はboxingされることによってヒープへコピーされ、IEnumerator<object>を継承したクラス内のパラメータ代入時にGCAllocが発生してしまう、nullであれば、object型はclassなので発生しないという形で動作します。

「Unity のコルーチン待機での yield retun 0 と yield retun null の違い」への1件のフィードバック

  1. ピンバック: コルーチン (Coroutines) [Unity] – Site-Builder.wiki

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください