여러 프레임에 나눠서 처리하기 때문에 도중에 레퍼런스가 바뀌면 다시 스캔해야할 필요성이 생긴다. 이 과정에서 오버헤드가 발생한다.
Unity 가비지 컬렉터의 문제점
Unity의 가비지 컬렉터는 다른 가비지 컬렉터, 주로 .Net의 가비지 컬렉터와 비교해 여러 단점을 가지고 있다.
- 세대 구분이 없어 빈번한 임시 할당을 효율적으로 제거할 수 없다.
- 압축 과정이 없어 외부 메모리 단편화를 해결할 수 없다.
2번 같은 경우 용량이 큰 객체를 메모리에 할당할 때, 충분한 공간이 없으면 가비지 컬렉터를 실행한 뒤, 여전히 공간이 없다면 힙을 확장해버린다.
가비지 최적화
가비지 컬렉션은 보통 비용이 많이 드는 작업이지만, Unity는 그 영향이 더 심하다. 따라서 아예 가비지를 만들지 않는 것이 최선이 된다.
- 오브젝트 풀 사용 : 객체를 파괴하지 않고 메모리에 유지시킨다. 활성화/비활성화를 통해 생성/삭제와 비슷한 효과를 낼 수 있다.
- String Builder 사용 : string은 기본적으로 읽기 전용이다. + 같은 연산자를 통해 문자를 추가하는 경우, 문자가 추가된 string을 새로 생성하기 때문에 이전에 할당했던 string은 가비지 컬렉션의 대상이 된다.
- 클로저 최소화 : 클로저를 간단하게 설명하면, 무명 함수나 람다식에서 스코프 외부의 변수나 함수를 참조하는 것을 의미한다. 이 외부 변수를 올바르게 전달하기 위해 익명 클래스를 생성하게되고, 보통 무명함수나 람다는 일회용 함수로 사용하기 때문에 외부변수를 캡쳐하기 위해 생성했던 객체의 레퍼런스를 바로 잃어버린다. 이것이 가비지 컬렉션의 대상이 된다.
- 박싱 최소화 : 주로 자동으로 값 타입에서 참조 타입으로 변환될 때 발생한다.
- 코루틴 : WaitFor계열 객체를 yield 연산자에서 생성하기보다 캐싱해서 사용하는 것이 바람직하다.
- Linq와 정규표현식 지양 : 박싱으로 인한 가비지가 발생한다.
- Unity API : 몇몇 Unity API는 가비지를 생성한다. NonAlloc이 붙은 함수를 사용하는 것을 권장한다. 특히 배열 기반일 경우 접근할 때 마다 사본을 생성하는 경우가 있어, 가능하면 캐싱해서 사용하는 것이 좋다.
- 빈 배열 반환 : 길이가 0인 배열을 반환할 때 새로 생성하기보다, 미리 정의된 정적 인스턴스로 반환하는 편이 효율적이다.