반복문 속도 비교

JJW·2024년 11월 22일
0

Unity

목록 보기
2/34

오늘은 Unity에서 사용되는 반복문에 속도 비교를 해보려고 합니다.

Unity에서 사용되는 반복문은 아래와 같습니다.


반복문 별 장단점 및 사용처

  • for 반복문
    • 장점
    • 가장 빠르고 단순한 반복문입니다.
    • 반복 횟수가 명확한 경우에 사용하면 속도가 빠릅니다.
    • 단점
    • 동적 조건 기반 처리에는 부적합
    • 사용 되는 곳
    • 반복 횟수가 정해진 작업에 사용됩니다.
  • while 반복문
    • 장점
    • 조건에 따라 반복 횟수를 유동적으로 조절이 가능합니다.
    • 단점
    • 무한 루프의 위험이 존재합니다.
    • 사용 되는 곳
    • 조건 기반의 유동적인 작업에 사용됩니다.
  • foreach 반복문
    • 장점
    • 컬렉션을 순회할 때 간결하고 안전하며 반복자를 자동으로 처리합니다.
    • 단점
    • 반복 중 컬렉션 수정이 불가능하며 내부적으로 반복자를 생성하여 속도가 약간 느립니다.
    • 사용 되는 곳
    • 배열,리스트,딕셔너리 등 순차적으로 데이터를 처리하는 경우에 사용됩니다.
  • do while 반복문
    • 장점
    • 최소 1회 실행을 보장하며 초기화 작업 후 조건을 확인 가능합니다.
    • 단점
    • 조건이 실패되도 한 번은 실행되어야 합니다.
    • 사용 되는 곳
    • 사용자 입력 초기화 후 조건 반복에 사용됩니다.
  • Linq
    • 장점
    • 데이터 필터링,변환, 정렬을 직관적으로 처리하며 가독성이 뛰어납니다.
    • 단점
    • 성능이 중요한 경우에는 오버헤드가 발생할 우려가 있으며 동적 계산 작업에는 부적합합니다.
    • 사용 되는 곳
    • 데이터 처리 및 가공 작업에 사용됩니다.
  • Task
    • 장점
    • 비동기적으로 실행되며 병렬 처리가 가능하고 CPU 집약적인 작업을 효율적으로 처리합니다.
    • 단점
    • 동기적으로 사용할 경우 퍼포먼스 이점이 사라지며 복잡성이 증가합니다.
    • 사용 되는 곳
    • AI 계산, 네트워크 요청 및 대규모 데이터 처리, 비동기 작업에 사용됩니다.

테스트 코드

using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;

public class LoopPerformanceTest : MonoBehaviour
{
    void Start()
    {
        const int iterations = 10_000_000;
        int[] array = new int[iterations];
        for (int i = 0; i < iterations; i++) array[i] = i;

        Stopwatch stopwatch = new Stopwatch();

        // for loop
        stopwatch.Start();
        for (int i = 0; i < iterations; i++)
        {
            int temp = array[i] * 2;
        }
        stopwatch.Stop();
        UnityEngine.Debug.Log($"for loop: {stopwatch.ElapsedMilliseconds} ms");

        // while loop
        stopwatch.Reset();
        stopwatch.Start();
        int j = 0;
        while (j < iterations)
        {
            int temp = array[j] * 2;
            j++;
        }
        stopwatch.Stop();
        UnityEngine.Debug.Log($"while loop: {stopwatch.ElapsedMilliseconds} ms");

        // foreach loop
        stopwatch.Reset();
        stopwatch.Start();
        foreach (var value in array)
        {
            int temp = value * 2;
        }
        stopwatch.Stop();
        UnityEngine.Debug.Log($"foreach loop: {stopwatch.ElapsedMilliseconds} ms");

        // LINQ
        stopwatch.Reset();
        stopwatch.Start();
        var linqResult = array.Select(n => n * 2).ToArray();
        stopwatch.Stop();
        UnityEngine.Debug.Log($"LINQ: {stopwatch.ElapsedMilliseconds} ms");

        // Task
        stopwatch.Reset();
        stopwatch.Start();
        Task.Run(() =>
        {
            for (int i = 0; i < iterations; i++)
            {
                int temp = array[i] * 2;
            }
        }).Wait();
        stopwatch.Stop();
        UnityEngine.Debug.Log($"Task: {stopwatch.ElapsedMilliseconds} ms");

        // do-while loop
        stopwatch.Reset();
        stopwatch.Start();
        int k = 0;
        do
        {
            int temp = array[k] * 2;
            k++;
        } while (k < iterations);
        stopwatch.Stop();
        UnityEngine.Debug.Log($"do-while loop: {stopwatch.ElapsedMilliseconds} ms");
    }
}

  • 10,000,000 반복 시 테스트 결과
  • 단순 곱셉이여서 차이가 별로 없는 듯합니다.
  • Linq의 경우에는 내부적으로 함수 호출과 람다를 통해 처리를 하여 속도가
    느린 걸 확인할 수 있습니다.

  • 100,000,000 반복 시 테스트 결과
  • 생각 했던 순서가 아니라 의외였습니다. for문이거나 Task문이 제일 빠를 줄 알았는데
  • foreach가 제일 빨랐으며 그 다음이 do - while문 이였습니다.
  • Task의 경우에도 비동기로 실행되서 빠를 줄 알았지만
    실행 자체는 동기적으로 대기하여 성능 차이가 없다고 합니다.

  • 단순 곱셉이 아니라 Mathf 클래스를 사용하여 복잡한 연산으로 변경해보았습니다.
// 기존 테스트 방식
int temp = value * 2;

// 변경 테스트 방식
float temp = Mathf.Sqrt(value) * Mathf.Sin(value) + Mathf.Log(value + 1);

  • 10,000,000 반복 시 테스트 결과
  • do-while문이 굉장히 빠르게 나와 왜 빠른지 찾아보았떠니 do-while문은 조건 평가를 반복문 끝에서 한 번만 실행하므로, 첫번째 반복에서의 조건 평가를 건너뛰기 때문이라고 하였습니다.
  • for문이 기존 단순 곱셈 방식에서의 속도보다 한참 느리게 나왔습니다.
  • foreach문은 여전히 준수한 속도를 보여줍니다.

  • 다른 Class의 변수에 접근하여 해당 변수에 대한 내용을 가져오도록 테스트 방식을 변경해보았습니다.
    public class tempdata
    {
        public string name { get; set; }
    }
    
    List<tempdata> dataList = new List<tempdata>();

    // 리스트 초기화
    for (int i = 0; i < iterations; i++)
    {
        dataList.Add(new tempdata { name = $"Name_{i}" });
    }
    
    string name = dataList[i].name;

  • 10,000,000 반복 시 테스트 결과
  • 다른 반복문의 속도는 별 차이가 없지만 forecah문에 속도가 현저히 떨어졌습니다.
  • foreach문에 경우 컬렉션을 순회하기 위해 반복자를 생성하면서 해당 반복자가
    다른 클래스의 변수에 접근하면서 해당 객체를 참조하고 데이터를 가져오는
    추가 작업을 수행하게 되면서 속도가 느려진다고 합니다.

테스트 결과

  • 기본 속도 순위
순위반복문
1do-while
2Task
3while
4for
5foreach
6Linq
  • 단순 연산 시 추천하는 반복문
    for 문, do-while 문, while 문

  • 컬렉션 순회 시 추천하는 반복문
    for 문, foreach 문

  • 조건 기반 반복 시 추천하는 반복문
    while 문, do-while 문

  • 데이터 변환 및 필터링 시 추천하는 반복문
    Linq , foreach 문

  • 비동기 처리 시 추천하는 반복문
    Task


느낀 점

생각보다 for문이 느리고 do-while문과 while문이 빠르단 걸 느꼈습니다.
근데 속도와 별개로 저 같은 경우에는 웬만하면 반복문은 for,foreach,Linq 정도 사용하는데
자료구조처럼 상황에 맞게 적절히 사용해야 하는 것 처럼
반복문도 상황에 맞게 사용하는 노련함을 길러야 할 것 같습니다.

  • Task를 반복문으로 구분한게 잘못된 것 같긴...
  • 제가 조사한 내용이 맞지 않거나 잘못 된 경우에 댓글로 잘못된 점 지적해주시면 감사합니다 ! (´._.`)
profile
Unity 게임 개발자를 준비하는 취업준비생입니다..

0개의 댓글