유니티 가비지 컬렉션 최적화하기 5 – 번역
유니티에서 가비지 컬렉션 최적화하기 5
– 원문 링크 –
이전글 – 불필요한 힙 할당을 발생시키는 주요 원인 1
코루틴 (Coroutine)
유니티가 코루틴을 관리하기 위해서 인스턴스를 생성해야만하는 클래스 때문에, StartCoroutine() 함수를 호출하면 소량의 가비지(Garbage)가 생성됩니다.
게임에서 퍼포먼스가 중요한 시점에는, StartCoroutine()함수의 호출을 제한하는 것이 좋습니다.
이런 방식으로 생성된 가비지를 줄이기 위해서는, 성능이 중요한 시점에 호출해야하는 코루틴을 모두 미리 시작시켜야 합니다.
그리고 StartCoroutine()의 지연 호출(Delayed calls)을 포함하는 중첩된 코루틴을 사용할때는 특히 주의해야 합니다.
코루틴 내의 yield 구문은 그 자체로는 힙 할당을 발생시키지 않습니다; 그러나, yield 구문에 전달하는 값이 불필요한 힙 할당을 발생시키는 경우가 있습니다.
예를 들어, 다음 코드는 가비지를 발생시킵니다:
yield return 0;
0의 값을 갖는 int 변수가 박싱 처리되기 때문에, 이 코드는 가비지를 생성합니다.
이 경우, 불필요한 힙 할당없이 단순히 한 프레임을 대기하려고 할때, 아래 코드를 사용하는 것이 좋습니다:
yield return null;
코루틴을 사용할때 자주 범하는 다른 공통된 실수는 동일한 값을 두번 이상 사용해서 yield 시킬때 new를 사용하는 것입니다.
예를 들어, 다음 코드는 루프가 반복될때마다, WaitForSeconds 오브젝트를 생성하고 삭제합니다:
while (!isComplete) { yield return new WaitForSeconds(1f); }
WaitForSeconds 오브젝트를 캐싱하고, 재사용하면 가비지가 훨씬 적게 생성됩니다. 다음 코드는 이 방법을 예제로 보여줍니다:
WaitForSeconds delay = new WaitForSeconds(1f); while (!isComplete) { yield return delay; }
코루틴으로 인해서 많은 가비지가 생성되는 코드가 작성된 경우, 코루틴 대신 다른 기능을 사용하도록, 코드를 리팩토링하는 것이 좋습니다.
코드를 리팩토링하는 것은 매주 복잡한 주제이고, 모든 프로젝트는 각각 고유하지만, 일반적으로 코루틴 대신 사용할 수 있는 2가지 대안이 있습니다.
예를 들어, 코루틴을 사용해서 주로 시간을 관리하는 경우, Update() 함수에서 단순히 시간을 추적(기록)하는 방법을 사용할 수 있습니다.
코루틴을 사용해서, 게임에서 실행될 명령의 순서를 관리하는 경우, 오브젝트 간에 서로 통신할 수 있는 일종의 메시지 시스템을 만들어서 이를 대신 사용할 수 있습니다.
모든 상황에 대처할 수 있는 방법은 없지만, 코드에서 동일한 목적을 당성하는데는 여러 가지 방법을 사용할 수 있다는 점을 명심하세요.
foreach 루프 (foreach loops)
유니티 5.5 이전 버전에서는, 배열이 아닌 다른 항목에 대해서 foreach 루프를 반복시킬때, 각 루프가 종료될때마다 가비지가 생성됩니다.
이는 내부에서 발생하는 박싱(Boxing) 때문입니다. 루프가 시작될때 System.Object가 힙에 할당되고 루프가 종료될때 해제됩니다.
이 문제는 유니티 5.5 버전에서 수정되었습니다.
예를 들어, 유니티 5.5 이전 버전에서, 다음 코드의 루프는 가비지를 생성합니다:
void ExampleFunction(List listOfInts) { foreach (int currentInt in listOfInts) { DoSomething(currentInt); } }
유니티 버전을 업그레이드할 수 없는 상황이라면, 이 문제를 위한 간단한 해결책이 있습니다.
for 루프와 while 루프는 박싱을 발생시키지 않기때문에, 가비지를 생성하지 않습니다.
배열이 아닌 컬렉션(Collections)에 대해서 루프를 사용할때는 for 또는 while 루프를 사용하는 것이 좋습니다.
다음 코드는 가비지를 생성하지 않습니다:
void ExampleFunction(List listOfInts) { for (int i = 0; i < listOfInts.Count; i ++) { int currentInt = listOfInts[i]; DoSomething(currentInt); } }
함수 참조 (Function references)
익명 메소드(anonymous method)또는 명명 메소드(named method)에 관계없이, 함수에 대한 참조는 유니티의 참조 타입(Reference Type) 변수입니다.
따라서 힙 할당이 발생합니다.
익명 메소드를 클로저(closure)로 변환하면(익명 메소드를 작성한 시점에 스코프 내에 있는 변수에 접근한 경우), 상당한 양의 메모리와 힙 할당이 증가합니다.
함수 참조 및 클로저가 메모리를 할당하는, 정확하고 자세한 수치는 플랫폼과 컴파일러 설정에 따라 다릅니다.
하지만, 가비지 컬렉션이 걱정되는 수준이라면, 게임 플레이 중에 함수 참조 및 클로저의 사용을 최소화하는 것이 좋습니다.
LINQ 와 정규식 (LINQ ad Regular Expressions)
LINQ 와 정규식은 모두 내부에서 박싱(Boxing)을 발생시키기 때문에, 가비지를 생성합니다. 성능이 중요한 시점에서 이 두가지 모두의 사용을 피하는 것이 좋습니다.
내용 끝까지 읽어주셔서 감사합니다.
배너 클릭은 저에게 많은 힘이 됩니다.
감사합니다 🙂