Unity Null Check할 때 성능을 개선할 수 있는 방법
Null 체크로 해당 객체가 실제로 존재하는지 확인하는 일은 매우 자주 발생하곤 한다.
그런데 최근 IDE를 VS에서 Rider로 갈아탔더니 null 체크를 할 때마다 이것저것 알려주었고, 충격적인 사실을 여러가지 알게 되었다.
UnityEngine.Objects
UnityEngine.Object는 유니티 엔진에서 관리하는 오브젝트이다. 그리고 유니티는 실제로는 C++로 작성되어 있다.
그리고 C++은 가비지 콜렉터가 존재하지 않는 언어이다. 즉, 언어가 아닌 유니티가 가비지 컬렉터를 관리하고 있다는 뜻이다.
UnityObjects를 Destory를 한 뒤 null 체크를 했을 때, 로그에서는 null이 맞다고 뜨니 의심을 품지 않게 되기 마련이다. 그러나 실제로는 null로 객체를 초기화해주지 않았기 때문에, 가비지 컬렉팅이 일어나기 전까지 메모리에 남아 있게 된다.
그래서 유니티 오브젝트를 UnityEngine의 Object와 .net의 Object로 받아 null check를 해보면 다음과 같은 현상이 일어난다.
var obj = new GameObject();
yield return null;
Destroy(obj);
yield return null;
print($".Net Object : {(obj as System.Object) == null}");
print($"Unity Object : {(obj as UnityEngine.Object) == null}");
//결과
//.Net Object : False
//Unity Object : True
Unity의 Object가 ==, != 연산자를 오버로딩하여 네이티브 객체가 사라졌다면 null이라는 정보를 전달하고 있는 것이다.
이렇게 실제 메모리와 엔진의 판단이 다른 경우를 fake null이라고 한다.
Unity Objects를 null check 할 때의 퍼포먼스 이슈
위에서 ==, != 연산자를 오버로딩하여, 네이티브 객체가 사라졌는지 유무를 판단하여 전달한다고 했다.
그리고 이는 자연스럽게 코드 실행을 느리게 만든다. 이를 피하기 위해 ReferenceEquals로 비교하면 연산이 굉장히 빨라진다.
private int count = 100000000;
void Start()
{
var tf = transform;
var stopwatch = new Stopwatch();
// Do == null
stopwatch.Start();
NullCheck_NotNull(tf);
stopwatch.Stop();
print($"== : {stopwatch.ElapsedMilliseconds}");
// Reset StopWatch
stopwatch.Reset();
// Do ReferenceEquals
stopwatch.Start();
NullCheck_ReferenceEquals(tf);
stopwatch.Stop();
print($"referenceEquals : {stopwatch.ElapsedMilliseconds}");
}
private void NullCheck_NotNull(Object obj)
{
for (int i = 0; i < count; i++)
{
if (obj == null) { }
}
}
private void NullCheck_ReferenceEquals(object obj)
{
for (int i = 0; i < count; i++)
{
if (ReferenceEquals(obj, null) == true) { }
}
}
1억 번 실행한 결과, ReferenceEquals가 30배정도 빠름을 알 수 있다.
마냥 NullCheck를 위해 ReferenceEquals를 쓰는 건 안 된다
그러나, 성능 개선을 위해 ReferenceEquals를 남발하는 것은 코드를 꼼꼼히 체크한 뒤에 해야 한다.
public이나 serializeField로 선언한 UnityObject들은 아무것도 넣어놓지 않으면 Fake Null 상태가 되기 때문이다.
public Camera cam_public;
[SerializeField] Camera cam_serialize;
private Camera cam_private;
private void Start()
{
print($"cam_public is {ReferenceEquals(cam_public, null)}");
print($"cam_serialize is {ReferenceEquals(cam_serialize, null)}");
print($"cam_private is {ReferenceEquals(cam_private, null)}");
}
이 점을 주의하고 ReferenceEquals를 사용한다면, 성능 개선에 도움이 될 것이다.
'프로그래밍 > Unity' 카테고리의 다른 글
UniTask 파헤치기 (0) | 2022.09.27 |
---|---|
Unity Device Simulator 알아보기 (0) | 2022.07.18 |
어드레서블 에셋 (1) (0) | 2022.04.10 |
Unity TextMesh Pro Sprite : 이모티콘 및 아이콘을 런타임에 동적으로 생성 또는 변경하기 (2) | 2021.08.01 |
Unity UGUI InputField의 한글 입력 문제 해결하기 (1) | 2020.12.15 |
댓글
이 글 공유하기
다른 글
-
UniTask 파헤치기
UniTask 파헤치기
2022.09.27 -
Unity Device Simulator 알아보기
Unity Device Simulator 알아보기
2022.07.18 -
어드레서블 에셋 (1)
어드레서블 에셋 (1)
2022.04.10 -
Unity TextMesh Pro Sprite : 이모티콘 및 아이콘을 런타임에 동적으로 생성 또는 변경하기
Unity TextMesh Pro Sprite : 이모티콘 및 아이콘을 런타임에 동적으로 생성 또는 변경하기
2021.08.01