개발/공부

[유니티] 성능 테스트 환경 구축 / i++과 ++i의 속도 차이

메피카타츠 2023. 7. 22. 16:58

[결론]

요즘은 컴파일러들이 알아서 바꿔주기 떄문에 i++나 ++i나 별 차이 없다.

하지만 컴파일러가 바꿔준다는 것은 비효율적이라는 말이기도 하니 ++i로 작성하는 습관을 들이는 것이 좋겠다.

 

개발을 하다보면 성능에 대해서 고민이 들 때가 있다.

 

for문을 돌 때 ++i을 쓰는데, i++에 비해 속도가 얼마나 빠를까?

for문을 쓰는 것이 좋은가? 아니면 foreach문을 쓰는 것이 좋은가?

Vector3.normalized는 Vector3.Normalize()를 호출하기 때문에 Vector3.Normalize()를 호출하면 오버헤드가 적은데, 얼마나 빠를까?

Object.Instantiate()를 하고 SetParent, position, rotation 설정을 해주는 것과 Object.Instantiate()에서 한 번에 해주는 것에 얼마나 차이가 있을까?

 

등등... 다양한 것들에 대해서 문득 궁금해지는 순간들이 온다.

물론 이런 것들은 게임 개발에 있어서 성능 중 극히 일부분을 차지하는 것이긴 하지만, 알아두면 좋을 것 같다고 생각해서 궁금했던 것들을 한 번 테스트해보자는 생각을 했다.

 

이전에도 몇 번 테스트를 했던 경험이 있는데, 테스트를 조금 편하게 할 수 있으면 좋을 것 같아서 테스트 환경을 구축했다.

 

using System;
using System.Diagnostics;
using UnityEngine;
using Debug = UnityEngine.Debug;

public partial class Tester : MonoBehaviour
{
    [SerializeField] private string _testName = "";
    [SerializeField] private int _repeatTime = 0;
    [SerializeField] private byte _entireTestRepeatTime = 1;

    private string _currentTestName = "";
    private Stopwatch _stopwatch = new();

    public void StartTest()
    {
        for (int i = 0; i < _entireTestRepeatTime; ++i)
        {
            Invoke(_testName, 0);
        }
    }

    private void DoTest(string testName, Action<int> test, int repeatTime = 0)
    {
        int tempRepeatTime = _repeatTime;
        if (repeatTime > 0)
        {
            _repeatTime = repeatTime;
        }
        else if (repeatTime > _repeatTime)
        {
            Debug.LogError("repeatTime can't bigger than original repeatTime");
            return;
        }
        _currentTestName = testName;

        
        _stopwatch.Restart();

        test.Invoke(_repeatTime);

        _stopwatch.Stop();
        LogStopwatchElapsedMilliseconds();
    }

    private void LogStopwatchElapsedMilliseconds()
    {
        Debug.Log($"Test {_testName}/{_currentTestName} done, RepeatTime : {_repeatTime}, Takes {_stopwatch.ElapsedMilliseconds}ms");
    }
}

먼저 Tester.cs 파일에 Tester 클래스를 선언해주었다. 편한 사용을 위해서 인스펙터에서 테스트할 함수의 이름과 반복 횟수, 전체 테스트의 반복 횟수를 설정할 수 있도록 했다. Invoke로 함수 이름을 String으로 받아서 실행하는 것은 추적하기 힘들어지니 별로 좋은 방식은 아니지만, 여기서는 편리함을 중요시하여 사용하였다.

 

또, 실행 시간을 기록하거나 출력하는 것을 간편하게 처리해주도록 테스트를 실행할 함수를 Action으로 받아서 한꺼번에 처리해주도록 해주어서 테스트 코드를 간편하게 작성할 수 있도록 하였다.

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

public partial class Tester : MonoBehaviour
{
    private void NppAndppNTest()
    {
        DoTest("n++ Test", repeatTime => {
            for (int n = 0; n < repeatTime; n++) ;
        });

        DoTest("++n Test", repeatTime => {
            for (int n = 0; n < repeatTime; ++n) ;
        });
    }
}

테스트할 함수가 많아지면 스크립트가 많이 길어질 것 같아서 Tester를 partial 클래스로 나누었다. 위의 경우 NppAndppNTest.cs로 스크립트를 나누어 작성하였다. 그리고 Tester에서 사용할 함수라는 것을 쉽게 분별할 수 있도록 TestList 폴더에 모아두었다.

 

이로써 위와 같이 테스트할 함수만 형식에 맞게 작성해주면 테스트를 간편하게 수행할 수 있게 되었다.

using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(Tester))]
public class StartTestButtonEditor : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        Tester tester = (Tester)target;
        if (GUILayout.Button("Start Test"))
        {
            tester.StartTest();
        }
    }
}

또, 인스펙터에서 버튼을 눌러서 바로 테스트할 수 있도록 Editor도 추가해주었다.

 

결과적으로 이런 GameObject가 만들어졌다.

 

여기서 Start Test 버튼을 누르면...

 

이런 식으로 결과가 나온다.

3번 테스트를 수행해서 2개씩 3번 나눠서 봐야하는데, ++n이 약간 빠른가? 싶지만 마지막 결과는 n++이 빠르다.

왜 이런 결과가 나왔는지 의문이 들어서 찾아보니 요즘에는 컴파일을 하면서 n++과 같은 것들을 ++n으로 바꿔도 되는 경우(for문에서 사용하는 것처럼 단순히 증가만 시키는 경우) ++n으로 바꿔서 컴파일해준다고 한다.

 

그래서 결론적으로 n++을 쓰나 ++n으로 쓰나 상관없다는 것이다.

쩝... 뭔가 허무한 결과긴 하지만 컴파일러가 바꿔준다는 것은 비효율적이라는 말이기도 하니 n++보다는 ++n이라고 작성하는 습관을 들이는 것이 좋을 것 같다.