실험 목적
- 매 프레임 호출되는
Update()
메소드, 코루틴의 성능 비교
실험 조건
- 운영체제 : Windows 10
- 유니티 에디터 버전 :
2020.3.17f1
- 실행 환경 : 유니티 에디터, Windows Standalone Build(Mono, IL2CPP)
실험 대상
[1] Update()
- 각 컴포넌트마다
Update()
작성
UpdateEveryFrame.cs
1
2
3
4
public class UpdateEveryFrame : MonoBehaviour
{
private void Update() { }
}
[2] CustomUpdate()
- 한 컴포넌트의
Update()
에서 다른 컴포넌트들의CustomUpdate()
호출
CustomUpdateCaller.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CustomUpdateCaller : MonoBehaviour
{
private static CustomUpdateCaller singleton;
private List<CustomUpdateCallee> list = new List<CustomUpdateCallee>(100000);
public static void AddElement(CustomUpdateCallee element)
{
singleton.list.Add(element);
}
private void Awake()
{
singleton = this;
}
private void Update()
{
foreach (var item in list)
{
item.CustomUpdate();
}
}
}
CustomUpdateCallee.cs
1
2
3
4
5
6
7
8
public class CustomUpdateCallee : MonoBehaviour
{
private void OnEnable()
{
CustomUpdateCaller.AddElement(this);
}
public void CustomUpdate() { }
}
[3] Coroutine - null
- 무한 반복문 내에서
yield return null
을 통해 매프레임 코루틴 검사
CoroutineEveryFrame.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CoroutineEveryFrame : MonoBehaviour
{
private void Start()
{
StartCoroutine(CoRoutine());
}
private IEnumerator CoRoutine()
{
while (true)
{
yield return null;
}
}
}
[4] Coroutine - WaitForEndOfFrame
- 무한 반복문 내에서
yield return new WaitForEndOfFrame()
을 통해 매프레임 코루틴 검사
CoroutineEveryFrame.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CoroutineEveryFrame : MonoBehaviour
{
private void Start()
{
StartCoroutine(CoRoutine());
}
private IEnumerator CoRoutine()
{
while (true)
{
yield return new WaitForEndOfFrame();
}
}
}
실험 통제 컴포넌트
UpdateCoroutineManager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public class UpdateCoroutineManager : MonoBehaviour
{
private enum TestMode { Update, CustomUpdate, CoroutineNull, CoroutineEndOfFrame }
[SerializeField] private int testCount = 100000;
[SerializeField] private TestMode mode = TestMode.Update;
[SerializeField] private int startFrame = 250;
[SerializeField] private int frameCount = 100;
private void Awake()
{
switch (mode)
{
case TestMode.Update:
CreateUnits<Test_UpdateEveryFrame>();
break;
case TestMode.CustomUpdate:
GameObject go = new GameObject("GO");
go.AddComponent<Test_CustomUpdateCaller>();
CreateUnits<Test_CustomUpdateCallee>();
break;
case TestMode.CoroutineNull:
CreateUnits<Test_CoroutineEveryFrame>();
break;
case TestMode.CoroutineEndOfFrame:
CreateUnits<Test_CoroutineEndOfFrame>();
break;
}
void CreateUnits<T>() where T : MonoBehaviour
{
for (int i = 0; i < testCount; i++)
{
GameObject go = new GameObject("GO");
go.hideFlags = HideFlags.HideInHierarchy;
go.AddComponent<T>();
}
}
}
private float timeBegin;
private void Update()
{
if (Time.frameCount == startFrame)
{
Log($"Start : {Time.frameCount}");
timeBegin = Time.realtimeSinceStartup;
}
else if (Time.frameCount == (startFrame + frameCount))
{
float elapsedMS = (Time.realtimeSinceStartup - timeBegin) * 1000f / frameCount;
Log($"Average(ms) : {elapsedMS:F2}");
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPaused = true;
#endif
}
}
private void Log(string log)
{
logString = log;
}
private string logString = "";
private GUIStyle style;
private void OnGUI()
{
if (style == null)
{
style = new GUIStyle(GUI.skin.box);
style.fontSize = 48;
style.alignment = TextAnchor.MiddleCenter;
}
Rect r = new Rect();
r.x = Screen.width * 0.1f;
r.y = Screen.height * 0.1f;
r.width = Screen.width * 0.8f;
r.height = Screen.height * 0.2f;
GUI.Box(r, logString, style);
}
}
실험 방법
UpdateCoroutineManager
컴포넌트에서 테스트 개수, 테스트 모드, 측정 시작 및 소요 프레임을 설정한다.- 유니티 에디터, Windows Mono 빌드, IL2CPP 빌드 환경에서 측정 결과를 확인한다.
실험 결과 - 1. 유니티 에디터
…
[1] Update
[2] Custom Update
[3] Coroutine - null
[4] Coroutine - WaitForEndOfFrame
실험 결과 - 2. Standalone Build(Mono)
…
[1] Update
[2] Custom Update
[3] Coroutine - null
[4] Coroutine - WaitForEndOfFrame
실험 결과 - 3. Standalone Build(IL2CPP)
…
[1] Update
[2] Custom Update
[3] Coroutine - null
[4] Coroutine - WaitForEndOfFrame
실험 결과 정리
환경 | 대상 | 프레임당 평균 소요 시간(ms) |
---|---|---|
Unity Editor | Update() | 67.89 |
Unity Editor | Custom Update | 33.99 |
Unity Editor | Coroutine - null | 134.09 |
Unity Editor | Coroutine - WaitForEndOfFrame | 168.63 |
Mono Build | Update() | 19.83 |
Mono Build | Custom Update | 17.15 |
Mono Build | Coroutine - null | 68.48 |
Mono Build | Coroutine - WaitForEndOfFrame | 80.99 |
IL2CPP Build | Update() | 17.34 |
IL2CPP Build | Custom Update | 17.17 |
IL2CPP Build | Coroutine - null | 61.12 |
IL2CPP Build | Coroutine - WaitForEndOfFrame | 88.01 |
매 프레임 한 번씩 호출되는 경우,
어떤 플랫폼에서든 항상 Update()
가 코루틴보다 성능이 좋다.
주의사항
간혹 위 실험 결과를 오해하는 경우가 있는데,
Update()
를 통해 매 프레임 호출 vs 코루틴으로 0.1초마다 호출
이와 같은 경우에 대해 위 실험 결과를 대입해서는 안된다.
‘동일하게 매 프레임마다 한 번씩 호출하는 경우’에 대해서만 단순히 참고할 수 있는 정도로 이해하면 된다.
그러니까, 어차피 매 프레임마다 호출하는 데다가 관리상 문제도 없는 경우,
굳이 Update()
를 통해 호출하던 것을 코루틴으로 바꿀 필요는 없다는 의미다.
오히려 Update()
로 매프레임 호출하여 병목이 발생하던 지점을
코루틴의 yield return WaitForSeconds(float)
등으로 바꾸어 최적화를 이루었다면,
올바른 방향이니까 이 실험 결과를 보고 오해하지 않았으면 한다.