Unity C#에서 foreach와 GC(Garbage Collection)

유니티 스크립트로 C#을 사용합니다.


C#은 C++과 달리 명시적인 delete를 하지 않고 GC가 메모리 해제 작업을 하기 때문에

GC에서 시간을 많이 잡아먹으면 게임 프레임이 저하되는 문제가 발생할 수 있습니다.


GC 호출을 줄일 수 있는 방법에 대한 이야기들은 인터넷에 잘 정리된 문서가 많이 있는데

뭔가 꺼림직하면서 명확하지 않은 부분이 하나 있는데 그것이 foreach의 사용입니다.


Reducing Memory Usage in Unity, C# and .NET/Mono 에서 foreach에 대한 가이드 라인을 제시합니다.

Avoid using foreach(). It calls GetEnumerator() on your list type, which will allocate an enumerator on the heap just to throw it away. You’ll have to use the more verbose C++-style for(;;) syntax.

enumerator를 힙에 생성하기 때문에 C#의 foreach보다는 C++ 스타일의 for(;;;) 문을 사용하라는 이야기입니다.


그러나, Foreach, Garbage, and the CLR Profiler 는 다르게 이야기합니다.

Foreach, Garbage, and the CLR Profiler는 CLR Profiler를 사용하여 foreach를 사용할때 

GC가 발생하는지 여부를 테스트한 결과를 설명합니다. 핵심은 다음과 같습니다.

When doing a foreach over a Collection an enumerator WILL be allocated. 

When doing a foreach over most other collections, including as arrays, lists, queues, linked lists, and others: 

     if the collections are used explicitly, an enumerator will NOT be allocated. 

     if they are cast to interfaces, an enumerator WILL be allocated.

List<> 와 같이 명시적인 Collection을 foreach할 경우에는 enumerator가 할당되지 않지만,

IEnumerable과 같이 인터페이스를 사용하는 경우에는 enumerator가 할당된다고 이야기 합니다.

(정확한 번역은 아닙니다. 제가 이해를 잘못했을 수도 있지만, 웹페이지의 예제를 보면 큰 의미는 일치하다고 생각됩니다.)


두 문서에서 이야기하는 내용이 다르기때문에 foreach를 써도 되는건지,

정말로 Unity C#에서 foreach를 사용했을때 GC가 전혀 발생하지 않는 것인지 궁금했습니다.

Unity profiler에 GC에 대한 내용도 출력이 되니 테스트해봤습니다.


테스트는 Update()시 for(;;)와 foreach를 사용하여 List<>에 접근하여 해당 객체의 Update()를 호출하는것이 전부입니다.


결론은.. foreach에서 GC alloc이 24B 기록되었습니다.

24B가 무엇때문에 발생하는 것인지는 좀더 자세히 찾아봐야 알 것같습니다. 

적어도 Unity C#에서는 foreach가 GC를 전혀 발생시키지 않는다는것은 아닌것 같습니다.


좀더 정확한 이유를 알고 계시거나, 테스트에 문제가 있어서 결과가 잘못 나온것 같다고 

생각되는 부분들은 알려주시길 부탁드립니다. 저도 좀더 정확하게 파악하고 싶습니다.



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

public class Test
{
    public void Update() { }
}

public class GcTest : MonoBehaviour 
{
    private List _testList = new List();

	void Start () 
    {
        for (int i = 0; i < 100000; ++i)
        {
            Test test = new Test();
            _testList.Add(test);
        }
	}

    void Update() 
    {
        UpdateListUseFor();

        UpdateListUseForeach();     
	}

    void UpdateListUseFor()
    {
        for (int i = 0; i < _testList.Count; ++i)
        {
            Test test = _testList[i];
            test.Update();
        }
    }

    void UpdateListUseForeach()
    {
        foreach (Test test in _testList)
        {
            test.Update();
        }
    }
}