유니티에서 프레임이 가끔 튀는 이유
프로파일러를 켜면 대부분의 프레임이 16밀리초 안에 끝납니다. 그런데 몇 초에 한 번 30밀리초짜리가 섞입니다. 평균만 보면 문제가 없어 보이는데 화면에서는 확실히 걸립니다.
그 튀는 프레임을 열어 보면 GC.Collect 같은 이름이 붙어 있는 경우가 많습니다.
여기까지는 알려진 이야기입니다. 그다음이 문제입니다. 검색해서 나오는 글마다 답이 다릅니다. 세대가 어쩌고 하는 글도 있고, 세대 같은 건 없다는 글도 있습니다.
둘 다 맞습니다. 유니티에는 GC 가 하나가 아니기 때문입니다.
내 프로젝트가 어느 백엔드로 도는지부터 확인해야 합니다. 그걸 모르면 남의 답을 가져다 쓰게 됩니다.
백엔드가 셋이다
C# 코드는 그대로인데 그걸 실행하는 런타임이 여럿입니다. 그리고 런타임마다 GC 가 다릅니다.
| 백엔드 | 무엇 | GC |
|---|---|---|
| Mono | 지금까지의 기본. 에디터와 데스크톱 | Boehm. 보수적·비압축·비세대 |
| IL2CPP | C# 을 C++ 로 바꿔 빌드. 모바일·콘솔은 대개 이쪽 | Boehm. 보수적·비압축·비세대 |
| CoreCLR | 새로 들어오는 것. 요즘 .NET 이 쓰는 그 런타임 | 정밀·압축·세대 |
지금 어느 것으로 도는지는 프로젝트 설정에서 확인합니다.
Edit > Project Settings > Player > Configuration > Scripting Backend 입니다.
여기서 갈립니다. 세대 이야기가 나오는 글은 CoreCLR 이나 일반 .NET 을 보고 쓴 것이고, 세대가 없다는 글은 Boehm 을 보고 쓴 것입니다.
지금 벌어지는 일
유니티가 Mono 를 걷어내고 CoreCLR 로 갈아타는 중입니다. 6.7 에서 실험적으로 들어왔고, 6.8 에서 Mono 가 아예 없어집니다.
그런데 여기에 반드시 알아야 할 단서가 하나 붙습니다.
IL2CPP 는 이 변화의 영향을 받지 않습니다. 계속 Boehm 을 씁니다.
유니티 문서에 그대로 적혀 있는 내용입니다. IL2CPP 의 Boehm GC 동작에는 변경이 없다고요.
그러니까 이렇게 됩니다.
- 데스크톱으로 빌드하고 CoreCLR 을 쓴다 → 세대 GC 가 온다
- 모바일로 빌드한다 → IL2CPP 라서 계속 Boehm 이다. 세대 GC 는 안 온다
모바일 게임을 만들고 있다면 “곧 좋아진다” 는 이야기가 내 이야기가 아닐 수 있습니다. 이 시리즈를 쓰는 이유의 절반이 이것입니다.
어느 쪽이든 안 변하는 것
다행히 바닥에 깔린 두 가지는 런타임이 달라도 같습니다.
하나. GC 를 부르는 것은 시간이 아니라 할당입니다.
new 를 했는데 자리가 없을 때 그 자리에서 돕니다. 한가할 때 알아서 도는 것이 아닙니다.
그래서 프레임 한가운데서 돌고, 그 프레임이 튑니다.
둘. 뿌리에서 손이 닿는 것만 살아남습니다. 지금 실행 중인 메서드의 지역 변수, static 필드, 그런 것들이 뿌리입니다. 거기서 참조를 따라가 닿은 것은 살고 나머지는 지워집니다.
그러니 대응도 어느 쪽이든 같습니다. 할당을 줄이면 됩니다. GC 를 빠르게 만드는 것은 우리 손을 떠난 일이지만, GC 를 덜 부르는 것은 우리가 할 수 있습니다.
힙은 늘어나기만 한다
한 가지 더 알아 둘 것이 있습니다. 이건 유니티 특유의 성질입니다.
관리 힙에 자리가 모자라면 힙이 확장됩니다. 그런데 한 번 늘어난 힙은 거의 안 줄어듭니다. 안이 텅 비어도 유니티는 그 크기를 그냥 들고 있습니다. 확장할 때는 직전에 늘린 만큼의 두 배씩 늘립니다.
그래서 이런 일이 생깁니다.
로딩 화면에서 한 번 크게 할당했다고 해 봅시다. 힙이 그만큼 커집니다. 로딩이 끝나고 그 객체들이 전부 죽어도 힙 크기는 그대로입니다. 게임 내내 안 쓰는 메모리를 들고 있게 됩니다.
즉 힙 크기를 정하는 것은 평균 사용량이 아니라 가장 나빴던 한순간입니다. 피크를 한 번 찍으면 그 값이 남습니다.
정리
- 유니티에는 GC 가 하나가 아닙니다. 내 프로젝트가 어느 백엔드로 도는지부터 확인해야 남의 답을 가져다 쓰지 않습니다
- Mono 와 IL2CPP 는 Boehm 입니다. 보수적·비압축·비세대입니다
- CoreCLR 은 정밀·압축·세대이고, 데스크톱과 iOS 에 실험적으로 들어와 있습니다
- 관리 힙은 한 번 늘면 거의 안 줄어듭니다. 힙 크기를 정하는 것은 평균이 아니라 가장 나빴던 한순간입니다
- 그래서 로딩 화면의 피크 한 번이 게임 내내 따라다닙니다