Featured image of post [Memory 04] 전역 할당자·jemalloc·tcmalloc (전문)

[Memory 04] 전역 할당자·jemalloc·tcmalloc (전문)

전역 할당자 교체·튜닝의 효과와 한계, 워크로드·프레그멘테이션·스레드 경합 관점에서의 검증 절차를 전문 난이도로 정리합니다. Tr.02 문자열·컨테이너 경계와 충돌하지 않게 쓰는 실무 판단을 다룹니다.

본 장은 전문 난이도입니다. 전역 할당자(프로세스 기본 malloc/free 계열을 대체하거나 런타임 훅으로 감싸는 방식)를 바꾸면 다스레드 할당·프레그멘테이션·캐시 지역성이 함께 움직입니다. jemalloc, tcmalloc 계열은 자주 언급되지만, “설치하면 빨라진다” 수준으로 접근하면 회귀·디버깅 지옥으로 이어지기 쉽습니다. 다만 아래에서 다루듯 jemalloc은 2025년 중반부로 사실상 개발이 종료됐으므로, 신규 도입 판단에서는 이 변화를 먼저 반영해야 합니다.

2026년 시점의 할당자 후보 재평가

전역 할당자 선택지는 정적이지 않습니다. jemalloc 원저자 Jason Evans는 2025-06-12 “jemalloc postmortem"에서 Meta 내부 투자 축소·미완성 기술 부채(HPA 기능이 2016년부터 미완성)·리더십 부재를 이유로 “active upstream development has come to an end"라고 공식 선언했습니다. 저장소 자체가 archived 처리된 것은 아니고 커뮤니티 마이너 릴리스(5.3.1, 2026-04)는 이어지고 있지만, 신규 프로젝트가 jemalloc을 “표준 선택지"로 깔고 시작하는 것은 더 이상 기본값으로 권하기 어렵습니다.

이 공백을 메우는 두 후보를 함께 검토 대상에 넣습니다.

  • mimalloc: v1.9.10/v2.3.2/v3 계열이 모두 활발히 릴리스되고 있습니다(2026-04 기준). v3는 “first-class heaps”(임의 스레드에서 특정 heap에 할당하고 힙별 통계를 추적)를 도입했고, THP 감지 시 최소 purge 크기를 자동 조정해 THP 파편화를 줄이는 로직이 추가됐습니다.
  • snmalloc: 특정 할당/해제 쌍을 다른 할당자에 위임하는 기능이 추가되어 GWP-ASan 통합, Windows 기본 할당자 오버라이드(디투어링)에 활용할 수 있습니다.

이 표에서 tcmalloc은 Google이 계속 유지하는 산업 표준으로 남아 있으므로 상태 변화가 없습니다. 결론적으로 “jemalloc/tcmalloc 양자택일"이라는 이전 프레임은 더 이상 정확하지 않고, “tcmalloc/mimalloc/snmalloc 중 워크로드에 맞는 후보 + 유지보수 리스크가 커진 jemalloc"으로 재구성해서 판단해야 합니다. 이미 jemalloc을 운영 중인 시스템을 당장 교체할 필요는 없지만, 버전 고정 정책(체크리스트 13번)에 “커뮤니티 유지보수 지속 여부를 정기적으로 재확인한다"는 조건을 추가하는 것을 권장합니다.

무엇을 바꾸는가

전역 할당자 튜닝은 보통 다음 중 하나 이상을 목표로 합니다.

  • 스레드 간 경합 완화: 중앙 락 병목을 줄이기 위해 per-thread 캐시·arena를 둡니다.
  • 할당 크기별 경로 분리: 작은 객체와 큰 객체를 다른 경로로 처리합니다.
  • 프레그멘테이션 완화: 장기 실행 프로세스에서의 메모리 사용량 추세를 안정화합니다.
  • 지연 분포 개선: 평균보다 꼬리에서 할당기가 튀는 경우를 줄입니다.

동시에 Tr.02에서 논의되는 문자열 SSO, 컨테이너의 커스텀 할당자, 스택 기반 버퍼 같은 기법과 경계가 겹칩니다. 애플리케이션이 이미 할당을 크게 줄였다면 전역 교체 이득이 작아질 수 있습니다.

워크로드 의존성

같은 바이너리라도 다음에 따라 결과가 갈립니다.

  • 객체 크기 분포: 작은 할당이 폭주하는지, 드물게 큰 블록이 나오는지.
  • 수명 분포: 초단수명 vs 장수명 객체 비율.
  • 스레드 수: per-thread 캐시 설계가 이득인지 오버헤드인지.
  • NUMA: 소켓 간 할당이 섞이면 이야기가 달라집니다(Tr.06·Tr.05과 연계).

따라서 “벤치 한 줄”보다 프로덕션 프로파일에서의 할당 패턴을 먼저 요약하는 것이 전문가적인 접근입니다.

검증 절차(최소한)

  1. 기준선: 기본 할당기에서 할당 카운트·지연 분포·RSS 추세를 기록합니다.
  2. A/B: 동일 바이너리·동일 트래픽에서 할당기만 교체합니다.
  3. 회귀 스위치: 한 플래그로 되돌릴 수 있게 합니다.
  4. 디버그 빌드: ASan/TSan 등과의 공존 정책을 문서화합니다(충돌이 흔합니다).
  5. 장기 soak: 메모리가 천천히 새는지, 프래그멘테이션이 시간에 따라 악화되는지 봅니다.
flowchart LR
  subgraph decision ["도입 판단"]
    A["할당 병목 증거"]
    B["코드 층 최적화 여지"]
    C["운영 리스크"]
  end
  A --> B
  B --> C

C++와의 상호작용

  • 전역 new/delete 오버로드는 모듈 경계·서드파티 라이브러리와 섞이면 디버깅이 어렵습니다.
  • std::pmr, 컨테이너별 allocator는 전역 교체보다 국소적이지만 설계 비용이 있습니다.
  • 메모리 풀은 전역 할당기와 중복 캐싱이 될 수 있어, “이중 관리”를 경계해야 합니다.

운영·보안 관점

일부 환경에서는 바이너리에 비표준 런타임을 끼워 넣는 것 자체가 정책 검토 대상입니다. 규제 산업에서는 공급망·감사 로그에 할당기 버전까지 포함해야 할 수 있습니다(Tr.11).

흔한 실패 패턴

  • 개발자 랩탑에서만 벤치: 서버 NUMA·컨테이너 cgroup 한도와 괴리.
  • p99 무시: 평균은 개선됐는데 꼬리가 나빠져 SLO 위반.
  • 디버그 이슈: 크래시 덤프·심볼과 맞지 않는 메타데이터.
  • 플랫폼 혼동: Linux에서 이득이 Windows에서 손해인 사례.

마무리

전역 할당자 튜닝은 강력한 레버이지만 애플리케이션 할당 패턴을 바꾸는 것만큼 값비싼 변경은 아닙니다. 먼저 Tr.02·본 트랙 기초(챕터 15)에서 불필요한 할당을 줄이고, 증거가 남을 때만 본 장의 절차로 들어오는 순서를 권합니다.

부록: 체크리스트 20

  1. hot 경로에서 malloc 빈도가 프로파일 상위인가?
  2. 스레드 수 증가에 할당 지연이 비선형으로 나쁜가?
  3. RSS가 시간과 함께 들쭉날쭉한가?
  4. 커스텀 풀·arena가 이미 있는가?
  5. 서드파티가 자체 할당기를 가정하는가?
  6. 정적 링크 vs 동적 링크 중 무엇인가?
  7. 컨테이너 이미지에 런타임이 포함되는가?
  8. 롤밹 플랜이 있는가?
  9. 성능·메모리 두 축을 동시에 볼 것인가?
  10. p99/p999를 계약에 넣을 것인가?
  11. ASan 빌드 파이프라인은?
  12. 크로스 플랫폼 지원 범위는?
  13. jemalloc/tcmalloc 버전 고정 정책은?
  14. 설정 튜닝(arena 수 등) 문서화는?
  15. 관측 지표(할당 지연 히스토그램)가 있는가?
  16. 메모리 한도 초과 시 OOM 킬 타이밍은?
  17. 샤딩된 프로세스마다 동일 설정인가?
  18. 카나리 배포 가능한가?
  19. 사고 시 원인 분기(앱 vs 할당기)가 가능한가?
  20. 팀 합의(Tr.11)가 있는가?

부록: Tr.02과의 경계 문장

Tr.02은 “코드가 어떤 타입으로 무엇을 할당하는가”에 가깝고, 본 장은 “그 요청을 런타임이 어떻게 처리하는가”에 가깝습니다. 둘 다 손대면 효과가 크지만, 동시에 바꾸면 회귀 원인 분석이 어려우므로 한 번에 하나의 레버를 우선합니다.