본문 바로가기

유니티/프로젝트 최적화

[Unity] 스크립팅 최적화 (5) - 트랜스폼 (Transform)

반응형

스크립팅에서의 트랜스폼이란?

언리얼의 씬 컴포넌트와 마찬가지로 유니티는 트랜스폼 컴포넌트가 씬에 배치된 오브젝트의 크기/방향/위치 등을 조작할 수 있는 유일한 수단이다. 따라서 그만큼 스크립트에서 자주 사용하게 되는데, 많이 사용하는 만큼 최적화할 방법이 있을까?


트랜스폼 캐싱 (GameObject.transform vs transform caching)

일반적인 트랜스폼의 접근은 다음과 같다.

public GameObject player;

void Start()
{
	player.transform = DoSomething();
}

 

하지만 Update 내부처럼 아주 많이 사용되는 경우 다음과 같이 캐싱해두는것도 고려해봐야 한다.

public GameObject player;

private Transform playerTF;

void Start()
{
	playerTF = player.transform;
}

void Update()
{
	// do player something here
}

 

왜냐면 transform 조차도 캐싱을 했을 경우 캐싱으로 얻어지는 이점을 가질 수 있기 때문이다.

 

다만 캐싱이 단점이 있듯, transform을 캐싱하는 것 역시 장점만 가지는 것은 아니다.

transform을 캐싱하면 메모리가 할당될 뿐만 아니라 그에 따라 GC도 동작하게 된다.

그리고 유니티가 업데이트되며 그 차이 또한 좁혀지고 있다고 한다. (정보에 따르면 IL2CPP일 경우 좀 더 높은 효과를 얻을 수 있다고 한다..!)

 

따라서 메모리와 성능 사이 적절한 지점을 잘 선택해서 transform을 캐싱해야 한다.


트랜스폼 계층구조 최소화하기

하이라키의 계층구조는 가급적이면 최소한의 구성을 갖고 있어야 한다. 또한 계층 구조의 변경 역시 적게 해야 한다.

그 이유는 계층구조를 관리하는 방식이 유니티 버전이 변경되면서 바뀌었기 때문인데,

 

5.3 이전 버전에 대한 트랜스폼 구성은 "최적화를 위한 유니티 가이드 원문"에 따라 다음과 같다.

 

Unity 이전 버전(5.3 버전 및 이전)에서는 Transform 구성 요소에 대한 참조가 일반적으로 무작위 순서로 메모리에 배치되었습니다. 이는 여러 개의 Transform 구성 요소를 반복하는 것이 캐시 미스가 발생할 가능성이 높아 상대적으로 느렸다는 것을 의미했습니다. 긍정적인 면은 GameObject을 다른 GameObject에 다시 부모로 설정하는 것이 상당한 성능 저하를 초래하지 않았다는 점이었습니다. 왜냐하면 Transform은 힙(heap) 데이터 구조와 유사하게 작동하여 삽입 및 삭제 작업이 상대적으로 빠르기 때문입니다. 이 동작은 우리가 제어할 수 없는 것이었기 때문에 우리는 그냥 받아들였습니다.

 

하지만 Unity5.4가 되면서 Transform의 메모리 레이아웃이 크게 변경되었다.

 

새로 변경된 하이라키 계층구조는 동적배열처럼 동작한다. 이는 하나의 그룹을 빠르게 서치 할 수 있게 해 주고 물리와 애니메이션 같은 시스템에 최적화되어있다.

 

반면 동적배열처럼 동작하기 때문에 생기는 문제점도 있다. 우리가 런타임도중에 동적배열의 크기를 변경하려고 한다면 크기에 맞는 배열을 새로 선언하고 할당해줘야 하지 않은가? 새로운 계층구조 시스템도 이와 비슷하다. 동적배열처럼 하나를 변경하더라도 전체 구조가 재정렬되는 것이다.

 

예를 들어, 다음과 같은 하이라키 transform계층 구조에서 GameObject (9)를 바꾼다고 하면 바꾸고자 하는 대상인 GameObject (9)만 다른 곳으로 옮겨지는 것이 아니라, 부모 오브젝트인 GameObject(1)부터 모두 재정렬되는 것이다. (옮기고자 하는 대상에도 마찬가지로 적용될 것이다.)

 

복잡한 계층 구조
간단한 계층 구조

하이라키 계층 구조가 복잡해지면 해질수록 더 최적화에는 불리하게 된다. 

따라서 우리는 이러한 복잡한 하이라키 대신 간단한 계층구조를 가지도록 설계해야 하며 하이라키의 변경을 최소화해야 한다.

 

추가적인 방법으로 transform의 hierarchyCapacity를 미리 지정해 주는 방법이 있다. 

이는 어떤 오브젝트를 Instantiate 할 때도 중요하게 작용한다.

 

예를 들어, 어떤 오브젝트를 Instantiate로 생성하면 생성된 오브젝트는 기본적으로 하이라키의 root 트랜스폼으로 지정된다.

그런데 그 오브젝트를 다른 오브젝트의 자식으로 넣는 경우, 처음 할당됐던 root 가 지워지고 새로운 트랜스폼에 재할당되기 때문에 불필요한 버퍼할당작업을 거치게 된다.

따라서 Instantiate로 생성할 때 인수로 transform을 지정하는 것이 좋다.

 

또 다른 방법으로는 Transform.hierarchyCapacity를 큰 수로 미리 지정해 두는 것이다.

미리 큰 버퍼 공간을 할당해두고 오브젝트가 생성될 때마다 공간을 재할당하는 것을 막는 것이다.

이는 동적배열에서 큰 메모리 공간을 미리 할당하는 것과 비슷하다.

반응형