스크램블 또는 Python으로 프로그래밍을 시작한다음, 게임을 개발해보겠다는 마음에 학습 곡선이 그나마 나은 Unity를 시작하면 얼마 지나지 않아 GC의 중요함을 볼 수 있다. GC 관리는 중요한게 사실이지만, 그 의의가 무엇인지 알아보겠다.
왜 GC를 해야합니까?
C#의 GC는 메모리 해제에 대해 휴먼 에러를 많이 겪었던 C++의 상황을 자동으로 처리 할 수 있게끔 고안되었다. 근데, 난 이게 궁금하지 않다. 이 문단에서는 그 근본을 먼저 알아본다. 역사책 시간이다.
우선 Unity의 GC 시스템은 2025년 10월의 6버전에서 두 개의 선택지가 있다.
Boehm GC
- 풀 네임은 Boehm-Demers-Weiser GC로, C/C++ 용 GC에서 출발했다. 개발자들의 이름을 땄으며, 우리나라로 치면 민수-유민-정택 쓰레기 처리기 되시겠다. 오.. 어쨌든, 1992년 논문에서부터 시작되었고 유지보수도 계속 이루어지고 있다.
A garbage collector for C and C++
A garbage collector for C and C++ [ This is an updated version of the page formerly at http://www.hpl.hp.com/personal/Hans_Boehm/gc, and before that at http://reality.sgi.com/boehm/gc.html and before that at ftp://parcftp.xerox.com/pub/gc/gc.html. ] The Bo
www.hboehm.info
- Unity는 2008년 Mono Runtime을 라이선스 할 당시 Mono의 기본 GC가 Boehm이었기 때문이다. Mono는 업그레이드 되면서 SGen GC로 전환했지만 Unity는 비용문제 내지 호환성 문제로 인해 Boehm을 계속 사용하고 있다. 어른들의 사정이 설명되는 순간이다.
- 간단하게 전환 전 후를 비교하면..
| 항목 | Boehm GC (전환 전) | SGen GC (전환 후) |
| 메모리 압축 | 없음 | 있음 |
| 할당 속도 | 느림 | 빠름 |
| GC 일시정지 | 지연시간이 길고, 힙 크기에 비례함 | 지연시간이 짧고, 예측도 가능 |
| 포팅 용이성 | 쉬움 (C/C++ 라이브러리 사용으로 인해) | 어려움 (런타임 통합 필요) |
| 안정성 | 높음 | 그냥 그럼 (구현이 복잡) |
SGen은 속도와 효율을 우선하고, Boehm은 이식성과 안정성을 우선한다.
Incremental GC (정확하게는 Boehm GC의 특정한 모드)
- Unity 2019.1 버전부터 제공되는 GC 모드로, 기본적으로는 켜져있다.
- 어떤 객체가 살아있는지 판단하는 marking phase를 여러 프레임에 분산시켜, 프레임당 일정 지연시간씩 나누어 처리한다.
- Boehm GC는 Stop-the-world 방식을 사용하는데, 한 프레임에 딱 모든 힙을 스캔해서 순간 렉 유발의 요인이 된다.
이렇게만 보면 완벽히 상위 호환이지만.. 제한사항이 있다.
- 참조가 빈번이 변경되는경우 오버헤드가 증가하며, marking phase가 끝나지 않으면 Boehm GC와 동일한 렉 유발을 내는 GC가 시작된다. 그리고 웹 플랫폼에서는 Incremental GC를 사용 할 수 없다.
- 예를 들면 이런거.
void Update() {
// 프레임마다 수십만 개의 참조가 바뀜
for (int i = 0; i < 100000; i++) {
enemies[i].target = players[Random.Range(0, players.Length)];
}
}
- 우리도 방이 치울만하면 금방 치우지만, 딱 보고 바로 치울 견적이 안나오면 날잡고 치웠던 기억이 한 번쯤 있다. 위의 코드 상황이 되어버리면 기존의 Stop-the-world가 발동되는 것이다.
Unity에서의 GC의 선택지는 이 두 개 밖에 없어요?
지금으로썬 그렇다. 좀 더 디테일하게 이야기해보면,
일반 개발자는 Unity 소스 코드를 읽을 권한조차 주어지지 않는다. 정확하게는 Unity Enterprise 플랜을 구독하면 소스 코드를 읽어볼 순 있는데, 이는 읽기 전용인데다 수정된 코드를 배포 할 수 없다.
싸제 GC를 구현하고 배포하려면 추가 라이선스가 필요한데, 이는 더 큰 플랜 지원이 필요하다. 즉 천문학적인 돈이 뒷받침 되어야한다.
그렇다고 대안이 없진 않다. IUnityMemoryManager 라는 API가 있다. 이 API를 통해 Memory Profiler에서 추적되는 Custom Allocator를 만들 수 있다. 대규모 블록을 할당해 내부적으로 서브 할당을 관리하는 저수준 오브젝트 풀링을 쓰는 방식을 고려 해볼 수 있다.
결론
Incremental GC 옵션 사용 여부와 GC 발동을 최소화하는 것은 이러한 뒷배경이 있었다. 물론 GC 작동을 최소화 하는 것이 무조건 좋은 코드라고 할 순 없다. 좋은 코드라는 기준은 가독성이 될 수도 있는데, GC.Alloc을 최소화 한다는 부분만 준수하기 위해선 코드 스타일을 박살을 내는 상황도 나올 수 있기 때문이다.
물론 게임 개발자의 고객은 직접 플레이하는 사람들이고 그 사람들 눈에는 잔렉 내는 코드가 바로 좋(같)은 코드일 것이다. 다음 내용으로는 트레이드오프가 뭐가 있을지에 대해 논해보겠다.
'컴퓨터 이론 > Unity' 카테고리의 다른 글
| [CSAPP&Unity #3] 흔한 걸 좀 덜 쓰는 방법 (1) | 2025.11.07 |
|---|---|
| 유니티 기본 개꼬여서 정리 (0) | 2025.10.24 |
| Deepdiving Garbage Collection : GC #2 (0) | 2025.10.22 |