9. 질문 — 메모리 할당자 심화 (tcmalloc/jemalloc, 단편화)
난이도 최상내 답안
모범답안
모범답안 — 메모리 할당자 심화와 단편화
난이도: 최상
핵심 답변
범용 malloc은 적절한 빈 블록을 찾고(free list 탐색), 메타데이터를 관리하며, 멀티스레드에선 힙 락 경합이 생긴다. tcmalloc/jemalloc은 스레드 로컬 캐시 + size class(크기별 분류) + arena 분할로 락 경합 없이 빠른 할당/반납을 한다. 메모리가 "반납했는데 안 줄어드는" 건 주로 외부 단편화(빈 공간이 조각나 큰 블록을 못 만들고, OS에 반납할 큰 연속 영역도 못 만듦) 때문이다. 근본 해법은 할당 자체를 줄이는 오브젝트 풀/arena.
깊이 있는 설명
malloc이 비싼 이유
- 요청 크기에 맞는 빈 블록을 free list에서 탐색, 분할(split)/병합(coalesce), 헤더 메타데이터 갱신.
- 멀티스레드: 단순 구현은 전역 힙 락 → 코어가 많을수록 경합. 작은 객체를 자주 할당하는 게임서버에서 치명적.
현대 할당자가 빠른 이유
- thread-local cache: 각 스레드가 작은 객체를 자기 캐시에서 락 없이 할당/반납. 비면 중앙에서 배치로 보충.
- size class: 임의 크기를 미리 정한 크기 등급으로 반올림 → 같은 크기 객체를 한 풀에서 관리, 탐색 불필요(내부 단편화는 약간 감수).
- arena/bin: 힙을 여러 arena로 쪼개 스레드를 분산 배정 → 락 경합 분산. jemalloc은 단편화 억제에, tcmalloc은 속도에 강점.
내부 vs 외부 단편화
- 내부 단편화: 요청보다 큰 블록을 줘서 블록 안에 낭비가 생김(size class 반올림 등).
- 외부 단편화: 전체 빈 공간은 충분한데 조각나 있어 큰 연속 블록을 못 만듦.
- "반납해도 RSS가 안 줄어듦": 할당자가 OS에서 받은 큰 영역(arena) 안에 살아있는 객체가 하나라도 있으면 그 영역을 OS에 반납 못 한다. 조각난 생존 객체들이 영역을 붙잡아 둠.
응용/실무 연결
- 게임서버는 수명이 비슷한 객체를 대량 생성/소멸(투사체, 패킷 버퍼, 이펙트) → 오브젝트 풀이 이상적. 풀은 size class 고정 + 반납이 곧 재사용이라 단편화·할당비용을 거의 0으로.
- 프레임/틱 단위 임시 할당은 arena/region 할당자(프레임 끝에 통째로 리셋)로 처리 → 개별 free 불필요.
- 할당자 교체(jemalloc/tcmalloc)는 코드 변경 없이 단편화·경합을 크게 개선하는 저비용 카드.
흔한 오답·함정
- "메모리 누수다" — 누수가 아니라 단편화일 수 있다. 객체 수는 안 느는데 RSS만 느는 패턴을 구분.
- 풀을 무한정 키워두기 → 피크에 맞춘 풀이 평상시 메모리를 점유. 상한·축소 정책 필요.
- size class 반올림을 무시 → 33바이트 요청이 48바이트 슬롯을 먹는 식의 내부 단편화 누적.
꼬리질문 대비
- Q. 오브젝트 풀의 위험은? 반납 후 재사용 시 상태 초기화 누락(이전 데이터 잔존), 이중 반납으로 같은 객체가 두 곳에서 쓰임 → 풀 무결성 검사 필요.
- Q. arena 할당의 한계? 개별 해제가 안 되므로 수명이 제각각인 객체엔 부적합. 동일 수명 묶음에만.
- Q. C++에서 커스텀 allocator를 어디에 끼우나? STL 컨테이너의 allocator 템플릿 인자, 또는
operator new오버로드/풀 기반 팩토리.