GC
글 19개
MemoryPack 의 byte[] 직렬화는 GC 가 치울 배열을 매번 남기고, 스레드마다 256KB 를 쥔다
MemoryPack 의 byte[] 직렬화는 호출마다 결과 배열을 새로 만들고, 스레드마다 256KB 작업 공간을 둡니다. 큰 결과 배열은 gen2 GC 를 부르고, 작업 공간은 스레드가 사는 동안 남으니 자주 도는 경로는 재사용 버퍼로 보냅니다.
MessagePack-CSharp 는 워밍업 뒤 결과물만 할당한다
직렬화 한 번이 힙에 남기는 것은 결과 배열 하나, 역직렬화는 만들어지는 객체뿐입니다. 재사용 버퍼면 0 B 입니다. 예외는 첫 호출과, 한 번에 128 KB 넘게 요청하는 큰 원시 배열입니다.
Zenject 의 풀 반납은 쉬는 객체 수만큼 비싸다
Zenject 의 MemoryPool.Despawn 은 이중 반납을 막으려고 쉬는 객체 스택을 끝까지 훑어, 풀이 클수록 반납이 느려집니다. 이 검사는 기본 설정에서 출시 빌드에도 남고, ZEN_STRIP_ASSERTS_IN_BUILDS 로 뺄 수 있습니다.
LitMotion 은 모션마다 드는 할당을 없앤 대신 러너마다 고정 비용을 낸다
LitMotion 의 Zero Allocation 은 모션을 만들고 돌리는 동안 GC 할당이 0 이라는 뜻입니다. 대신 타입과 타이밍마다 하나인 러너가 모션이 없어도 매 프레임 일해서, 모션이 적을수록 하나당 비용이 커집니다.
DOTween 은 트윈을 만들 때만 할당한다
DOTween 의 DOMove 한 번은 캡처 객체, 델리게이트 둘, 트윈 객체로 656 B 를 할당하고, 재활용은 기본으로 꺼져 있으며 켜도 람다 몫 280 B 가 남습니다. 도는 동안은 0 B 라, 줄이는 길은 만드는 횟수를 줄이는 것입니다.
DOTween 은 모든 트윈을 배열 하나에 두고 빈칸은 한 번에 메운다
DOTween 의 TweenManager 는 모든 트윈을 배열 하나에 만든 순서대로 두고, 뺄 때는 null 만 쓴 뒤 다음 갱신에 한 번에 당깁니다. 풀은 타입을 선형으로 찾고, 용량은 넘칠 때 2.5배로 늘며 할당이 몰립니다.
UniTask 의 에디터 할당은 코드 최적화가 만든다
에디터에서 UniTask 호출마다 찍히는 48 B 는 꺼진 코드 최적화가 상태 머신을 클래스로 만들어 생깁니다. 최적화를 켜면 0 이고, 그 뒤에도 남는 할당은 첫 대량 실행과 정상 경로 밖의 호출입니다.
UniTask 는 다 쓴 대기 객체를 풀에서 다시 쓴다
UniTask 대기를 반복해도 GC 가 없는 것은 다 쓴 대기 객체를 타입별 풀에 돌려 다시 쓰기 때문입니다. 풀은 할당도 락도 없고, 경합하면 기다리지 않고 포기하며, 가장 바빴던 순간만큼 자라서 줄지 않습니다.
UniTask 는 기다릴 때만 풀에서 빌려 GC 를 만들지 않는다
UniTask 의 async 메서드는 await 없이 끝나면 아무것도 만들지 않고, 정말 기다릴 때만 풀에서 러너를 빌렸다가 돌려줍니다. 이 0 은 최적화 컴파일에서만 성립하고, IL2CPP 에서는 반환이 프레임 끝으로 밀립니다.
관리 힙은 30MB 인데 게임은 2GB 를 먹는다
텍스처와 메시는 관리 힙에 거의 없습니다. GC 를 아무리 줄여도 여기는 안 줄어듭니다.
CoreCLR 이 와도 모바일은 그대로다
유니티가 Mono 를 걷어내고 세대 GC 를 들여옵니다. 그런데 IL2CPP 는 이 변화에서 빠져 있습니다.
증분 GC 는 일을 여러 프레임에 나눈다
기본으로 켜져 있고 프레임 튐을 줄여 줍니다. 다만 없애 주지는 않고, 공짜도 아닙니다.
보헴 GC 는 객체를 옮기지 않는다
보수적이고, 비압축이고, 비세대입니다. 이 세 단어가 프레임에서 어떻게 나타나는지 하나씩 봅니다.
유니티에서 프레임이 가끔 튀는 이유
평균은 멀쩡한데 몇 초에 한 번 걸립니다. 대개 GC 인데, 그 GC 가 지금 유니티에서 갈아 끼워지는 중입니다.
GC 가 있는데도 메모리는 샌다
치우는 사람이 있어도 우리가 안 놓으면 안 치워집니다. 가장 흔한 범인은 이벤트입니다.
소멸자는 객체를 한 번 더 살린다
안전망처럼 보이는데 언제 불릴지도 불리기는 할지도 정할 수 없습니다. 붙이면 오히려 늦게 치워집니다.
GC 가 치우지 못하는 것이 있다
힙은 GC 가 알아서 치웁니다. 파일 핸들과 소켓은 아닙니다. using 이 있는 이유입니다.
GC 는 한가할 때 도는 것이 아니다
자리가 모자랄 때 돕니다. 그래서 게임에서는 프레임 한가운데서 돌고, 그 순간 화면이 한 번 걸립니다.
오브젝트 풀은 언제 손해인가
풀링이 항상 이득이라는 통념을 실측으로 확인해봤고, 손해가 나는 구간이 분명히 있었다.