아카이빙/C#

[C#] 유니티에서 foreach 성능은 개선되었을까

셩님 2017. 5. 6. 08:26

예전에 잠깐 이슈가 되었던 유니티에서 foreach 성능문제를 다뤄보려고한다. 나도 그냥 foreach는 안좋구나 정도로만 알고 있었는데 이번 NDC 2017에서 오지현 에반젤리스트님의 강연에서 잠깐 지나가는 말로 유니티 5.2~3버전 쯤에서 foreach 성능이 개선되었다는 얘기를 들었다.

나는 foreach문이 코드가 훨씬 간결해서 선호하는 편인데, 성능 개선이 되었다니..! 이제 맘놓고 써도 되는걸까하는 기대를 가지고 한번 테스트를 해보기로 마음먹었다.

테스트는 GeekCoders님의 블로그 글을 참고하여 진행했다.

GeekCoder님은 유니티 4.6.1 기준으로 테스트했고, 그 결과 For문이 2.2배이상 빠른 것으로 나타났고, Foreach는 GC도 24B정도 남겼다.

Test 1. 프로파일러 모니터링

GeekCoder님의 코드를 그대로 사용했다.

ForUpdate.cs

public class ForUpdate : MonoBehaviour {

    List<string> strs = new List<string>();

    void Start () {
        for (int i = 0; i < Sample.loopCount; i++) {
            strs.Add (i.ToString ());
        }
    }

    void Update () {
        for (int i = 0; i < strs.Count; i++) {
            string str = strs [i];
        }
    }
}


ForeachUpdate.cs

public class ForEachUpdate : MonoBehaviour {

    List<string> strs = new List<string>();

    void Start () {
        for (int i = 0; i < Sample.loopCount; i++) {
            strs.Add (i.ToString ());
        }
    }

    void Update () {
        foreach (string s in strs) {
            string str = s;
        }
    }
}


EnumeratorUpdate.cs

public class EnumeratorUpdate : MonoBehaviour {

    List<string> strs = new List<string>();

    void Start () {
        for (int i = 0; i < Sample.loopCount; i++) {
            strs.Add (i.ToString ());
        }
    }

    void Update () {
        var enumerator = strs.GetEnumerator ();
        while (enumerator.MoveNext ()) {
            string str = enumerator.Current;
        }
    }
}


Sample.cs

public class Sample : MonoBehaviour {

    public const int loopCount = 100000;

    void Start()
    {
        this.gameObject.AddComponent<ForUpdate> ();
        this.gameObject.AddComponent<ForEachUpdate> ();
        this.gameObject.AddComponent<EnumeratorUpdate> ();
    }
}


결과



For문이 Foreach 보다 약 1.8배 빠른 것으로 나타났다. GeekCoders님의 테스트에서 나타난 2.2배보다 약간 빨라졌다고 하지만 크게 성능이 개선되었다고 볼 수는 없다.

다만 GC를 남기지 않은건 큰 의미가 있다고 본다. 성능 이슈면에서 크게 문제가 되지 않는다면 부분적으로 활용해도 될듯하다.



Test 2. ElapsedTime 테스트

Test 2는 단순히 1억번 실행했을 때 시간이 얼마나 걸리는 지를 테스트해봤다.


Sample2.cs

public class Sample2 : MonoBehaviour {

    const int loopCount = 100000000;

    List<string> strs = new List<string>();

    void Start () {
        for (int i = 0; i < Sample2.loopCount; i++) {
            strs.Add (i.ToString ());
        }

        System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew ();

        //warmup
        ForLoop ();
        ForEachLoop ();
        EnumeratorLoop ();


        //ForLoop()
        sw.Reset ();
        sw.Start ();
        ForLoop ();
        sw.Stop ();
        Debug.Log (string.Format ("ForLoop() : {0}", sw.ElapsedMilliseconds));

        //ForEachLoop()
        sw.Reset ();
        sw.Start ();
        ForEachLoop ();
        sw.Stop ();
        Debug.Log (string.Format ("ForEachLoop() : {0}", sw.ElapsedMilliseconds));

        //EnumeratorLoop()
        sw.Reset ();
        sw.Start ();
        EnumeratorLoop ();
        sw.Stop ();
        Debug.Log (string.Format ("EnumeratorLoop() : {0}", sw.ElapsedMilliseconds));
    }

    void ForLoop(){
        for (int i = 0; i < strs.Count; i++) {
            string str = strs [i];
        }
    }

    void ForEachLoop(){
        foreach (string s in strs) {
            string str = s;
        }
    }

    void EnumeratorLoop(){
        var enumerator = strs.GetEnumerator ();
        while (enumerator.MoveNext ()) {
            string str = enumerator.Current;
        }
    }
}


결과




For문이 Foreach 보다 약 1.7배 빠른 것으로 나타났다. 테스트1의 결과랑 거의 비슷하다.

모노나 닷넷 쪽 지식은 거의 전무하기 때문에 결론을 제시할 수는 없지만 결과만 참고하면 좋을듯하다.

'아카이빙 > C#' 카테고리의 다른 글

[C#] this() 생성자  (0) 2018.06.18
[C#] 얕은 복사와 깊은 복사  (0) 2018.06.18
[C#] static 필드와 메소드  (0) 2018.06.18
[C#] FieldInfo와 PropertyInfo  (0) 2017.05.06
[C#] Activator.CreateInstance와 new 차이  (4) 2017.04.15