✍️ 필사 모드: 메모리 할당자 완벽 가이드 — ptmalloc, jemalloc, tcmalloc, mimalloc: 내부 구조, 단편화, 멀티스레드, NUMA, 보안 (2025)
한국어들어가며 — 할당자는 보이지 않지만 어디에나 있다
malloc을 호출해본 모든 사람이 메모리 할당자를 사용해본 셈이다. 그러나 그 안에서 무슨 일이 일어나는지 아는 사람은 드물다. C 코드의 malloc(1024) 한 줄이 실제로는 수백 줄의 정교한 알고리즘을 거친다. 어떤 사이즈 클래스에 속하는지, 어떤 스레드의 캐시에 빈 공간이 있는지, arena를 잠가야 할지, 새 청크를 OS에서 받아야 할지 — 모든 결정이 마이크로초 단위로 일어난다.
이 결정들이 잘못되면 단편화로 인해 메모리 사용이 두 배가 되거나, 멀티스레드 락 경쟁으로 처리량이 절반으로 떨어지거나, 보안 취약점이 생긴다. Redis가 jemalloc을 쓰는 이유, Firefox가 mozjemalloc을 만든 이유, Chrome이 PartitionAlloc을 직접 만든 이유는 모두 이 결정들에서 나온다.
이 글은 메모리 할당자의 모든 것을 다룬다. 1987년 Doug Lea의 dlmalloc에서 시작해 ptmalloc, jemalloc, tcmalloc, mimalloc까지의 진화, 각 할당자의 내부 구조, 단편화의 본질, 멀티스레드 확장성의 어려움, NUMA, 보안, 그리고 실전 적용 사례까지. 1,300줄에 달하지만 모든 절은 독립적으로 읽을 수 있다.
이 글은 시리즈 두 글의 자매 작품이다:
- Linux 메모리 관리 딥다이브 — 커널이 페이지를 어떻게 관리하나
- 가비지 컬렉션 딥다이브 — 런타임이 객체를 어떻게 관리하나
이 글은 그 둘 사이 — 사용자 공간 할당자가 raw 바이트를 어떻게 관리하나를 다룬다. 셋이 모이면 메모리 계층의 풍경 전체가 그려진다.
1. 할당자가 푸는 문제
1.1 OS는 큰 단위로만 준다
OS는 메모리를 페이지(4KB) 단위로 준다. mmap 한 번에 최소 4KB. 그러나 사용자 코드는 malloc(8), malloc(24), malloc(123) 같은 작은 요청을 한다.
매번 mmap을 호출하면 두 가지 문제:
- 메모리 낭비 — 8바이트 요청에 4KB 페이지를 통째로 줌
- 시스템 콜 오버헤드 — 매
malloc이 syscall이면 매우 느림
해결: 할당자가 OS에서 큰 청크 (수십 KB-수 MB)를 받아두고, 그 안에서 작은 요청을 잘라서 분배.
1.2 free의 책임
free(ptr)을 호출하면 그 메모리가 다시 사용 가능해야 한다. 그러나 어떤 청크의 어느 부분이었는지 어떻게 알까? 그리고 인접 free 영역들과 합쳐서 큰 영역으로 만들 수 있을까?
이를 위해 할당자는 메타데이터를 유지한다. 청크별 헤더, free list, bitmap 등. 메타데이터가 너무 많으면 오버헤드, 너무 적으면 free가 어려워진다.
1.3 단편화 (Fragmentation)
할당과 해제를 반복하다 보면 단편화가 일어난다:
- External fragmentation: free 영역이 작게 흩어져 있어서 큰 할당이 안 됨. 합이 1MB여도 가장 큰 free 영역이 100KB면 200KB 할당 실패.
- Internal fragmentation: 할당된 청크 안에 사용 안 되는 공간. 24바이트 요청에 32바이트 청크를 주면 8바이트 낭비.
할당자의 핵심 도전 중 하나가 단편화 최소화.
1.4 멀티스레드
여러 스레드가 동시에 malloc을 호출하면 어떻게 될까? 단순한 글로벌 락은 락 경쟁으로 처리량이 폭락한다. 모던 할당자들은 thread-local cache, per-thread arena, lock-free free list 같은 기법으로 락 경쟁을 회피한다.
1.5 한 줄 요약
메모리 할당자는 "OS에서 받은 큰 메모리를 작게 잘라서 사용자 코드에 빠르고 효율적으로 분배하는" 시스템이다. 빠르고 (1us 이내), 효율적이고 (단편화 적음), 멀티스레드에 강해야 한다.
2. 역사 — dlmalloc에서 mimalloc까지
2.1 1987 — Doug Lea의 dlmalloc
Doug Lea가 작성한 첫 범용 malloc. 단순하고 효율적이고 portable했다. 거의 모든 후속 할당자의 기반이 되었다. ANSI C 표준 라이브러리에 들어갔고, 한 시대의 표준이었다.
핵심 아이디어:
- Boundary tag (chunk 양쪽에 메타데이터)
- Best-fit + immediate coalescing (인접 free 영역 즉시 합치기)
- Bin 기반 free list (사이즈별 분류)
2.2 1996 — Wolfram Gloger의 ptmalloc
dlmalloc은 단일 스레드용이었다. Wolfram Gloger가 멀티스레드 지원을 위해 fork. 핵심 추가: arena. 여러 개의 독립적 dlmalloc 인스턴스를 두고, 각 스레드가 자기 arena를 사용. 락 경쟁 완화.
ptmalloc은 glibc에 통합되어 Linux의 기본 malloc이 되었다. 30년이 지난 지금도 그렇다.
2.3 2005 — Jason Evans의 jemalloc
Jason Evans가 FreeBSD를 위해 작성. 처음부터 멀티스레드와 단편화 회피를 목표. Mozilla가 Firefox 3에 채택 (mozjemalloc), Facebook이 자기 인프라 전체에 채택. 대형 서버 워크로드의 사실상 표준이 되었다.
2.4 2007 — Google의 tcmalloc
Sanjay Ghemawat 등이 Google에서 작성. Thread-Caching Malloc의 줄임말. 핵심 아이디어: 매우 적극적인 thread-local cache. 스레드가 자기 캐시에서 작은 할당을 처리하면 글로벌 데이터 구조를 만지지 않음.
2.5 2019 — Microsoft Research의 mimalloc
Daan Leijen이 Microsoft Research에서 작성. 가장 새로운 메이저 할당자. Free list sharding과 heartbeat 메커니즘으로 단편화와 락 경쟁을 동시에 해결. 작고 (10K LOC 이하), 빠르고, 메모리 효율도 좋다.
2.6 그 외
- PartitionAlloc (Chrome): 보안 우선. 사이즈와 타입별로 partition 분리.
- Hoard (1998): 학계 출신 thread-cache 할당자. tcmalloc의 영감.
- scudo: LLVM의 hardened allocator. Android에서 기본.
- OpenBSD malloc: 보안 우선.
- musl mallocng: musl libc의 새 malloc.
각자 다른 트레이드오프를 만든다.
★ Insight ─────────────────────────────────────
- 할당자는 거의 학술 분야가 아니다: 알고리즘은 1990년대에 대부분 연구되었지만, 실제로 잘 동작하는 것을 만드는 것은 엔지니어링이다. 매 cycle의 비용, 매 캐시 라인의 위치, 매 락의 contention이 중요하다. 학술 논문보다 코드 베이스를 읽는 것이 더 많이 배운다.
- dlmalloc의 영원한 그림자: 30년이 지났지만 dlmalloc의 boundary tag, bin 구조, immediate coalescing은 여전히 모든 모던 할당자에 흔적을 남긴다. ptmalloc은 거의 dlmalloc + arena. jemalloc/tcmalloc/mimalloc은 다른 길을 갔지만 기본 어휘는 같다.
- 할당자 선택은 종종 정치다: Facebook은 jemalloc, Google은 tcmalloc, Microsoft는 mimalloc. 각자 자기 회사의 할당자를 자기 인프라에 박는다. 기술적 우열보다는 "내 회사가 만든 거니까"인 경우가 많다 — 그러나 실제 차이는 크다.
─────────────────────────────────────────────────
3. 할당자의 기본 어휘
3.1 Chunk
할당자가 사용자에게 주는 메모리 단위. 보통 헤더 + 사용자 영역 + (옵션) 푸터로 구성. 헤더에는 크기, free 여부, 다른 메타데이터.
+--------+-----------------+---------+
| header | user data | footer |
+--------+-----------------+---------+
^ ^
| |
metadata next chunk
malloc(size)는 size 바이트의 user data를 가진 chunk를 반환한다. 실제 크기는 size + header + footer.
3.2 Free List
해제된 chunk들의 연결 리스트. free(ptr) 시 chunk를 적절한 free list에 추가. malloc(size) 시 free list에서 적절한 크기의 chunk를 꺼냄.
단순한 단일 free list는 검색이 O(N)이라 느리다. 그래서 사이즈별 분류를 한다.
3.3 Bin
같은 (또는 비슷한) 사이즈 chunk들의 free list. ptmalloc은 약 130개의 bin을 가진다 — fastbins, smallbins, largebins, unsorted bin.
bin 구조의 장점: 검색이 O(1)에 가까움. 사이즈를 인덱스로 변환하고 해당 bin에서 첫 chunk를 꺼냄.
3.4 Arena
독립적인 chunk 풀과 그에 딸린 메타데이터 + 락. 멀티스레드 할당자는 여러 arena를 둬서 락 경쟁을 회피한다.
ptmalloc의 arena 수 기본값: 8 * num_cpus (보통). jemalloc은 더 적극적인 정책.
3.5 Best-fit, First-fit, Next-fit
free list 탐색 전략:
- First-fit: 처음 만난 충분히 큰 chunk 사용. 빠르지만 단편화 잘 일어남.
- Best-fit: 가장 작은 충분히 큰 chunk 사용. 단편화 줄지만 검색 비용 큼.
- Next-fit: 마지막 검색 위치부터 다시 시작. 캐시 친화적이지만 단편화 큼.
- Segregated: 사이즈별 bin으로 분류. 사실상 best-fit + O(1).
거의 모든 모던 할당자가 segregated 변형을 쓴다.
3.6 Slab Allocation
같은 사이즈 객체를 위한 전용 풀. 페이지를 같은 사이즈로 잘라서 객체 슬롯들로 만든다. 할당/해제는 매우 빠르다 — 비트맵 또는 free list만 만짐.
Linux 메모리 글에서 본 SLUB이 같은 아이디어. 사용자 공간 할당자도 비슷한 기법을 쓴다.
3.7 Coalescing
인접 free chunk들을 합쳐서 더 큰 free chunk로 만드는 것. 단편화 회피의 핵심. 즉시(immediate) 또는 지연(deferred)될 수 있다.
4. ptmalloc — glibc의 malloc
ptmalloc은 30년 전 만들어졌고 30년 동안 점진적으로 진화했다. 가장 많은 코드가 돌고 있는 할당자이지만, 가장 단점이 많은 할당자이기도 하다.
4.1 Chunk 레이아웃
+----------------+
| prev_size | 4 또는 8 bytes (이전 chunk가 free일 때만 의미 있음)
+----------------+
| size + flags | 3 LSB는 플래그 (PREV_INUSE, IS_MMAPPED, NON_MAIN_ARENA)
+----------------+
| user data |
| or |
| fd, bk, ... | (free일 때 다음/이전 free chunk 포인터)
| ... |
+----------------+
prev_size: 이전 chunk의 크기 (이전이 free일 때만 유효, coalescing용)size: 자기 크기 + 3 플래그 비트PREV_INUSE: 이전 chunk가 in-use인가IS_MMAPPED: 이 chunk가 mmap으로 직접 받았는가NON_MAIN_ARENA: main arena가 아닌 thread arena의 chunk인가
4.2 Arena
- Main arena: 메인 스레드용. heap에서 sbrk로 확장.
- Thread arena: 다른 스레드들이 동적으로 만듦. mmap으로 영역 받음.
각 arena는 자기 락을 가진다. 같은 arena에 동시 접근하는 스레드들은 락을 경합한다.
기본 arena 수 한도: MALLOC_ARENA_MAX = 8 * num_cores. 환경 변수로 조정 가능.
MALLOC_ARENA_MAX=2 ./my-app # arena 2개로 제한 (메모리 절약)
4.3 Bin 구조
ptmalloc의 핵심 자료구조. 약 130개의 bin:
- Fastbins (10개): 16-80 바이트 사이즈. 매우 빠른 LIFO 처리. coalescing 안 함.
- Smallbins (62개): 16-512 바이트. 같은 사이즈끼리.
- Largebins (63개): 512+ 바이트. 사이즈 범위별.
- Unsorted bin (1개): 최근 free된 chunk가 임시 머무는 곳. 다음 malloc 때 정렬.
4.4 Tcache (Linux glibc 2.26+)
매우 중요한 진화. 각 스레드별 캐시. 작은 chunk를 자기 캐시에 보관해서 arena 락 없이 처리.
// 의사 코드
void *malloc(size_t size) {
int idx = size_to_tcache_idx(size);
if (tcache[idx]) {
void *ptr = tcache[idx];
tcache[idx] = *(void **)ptr; // 다음 슬롯 가리킴
return ptr;
}
// tcache 미스 — arena로
return arena_malloc(size);
}
기본 설정: 각 사이즈 클래스당 7개 chunk, 총 64 사이즈 클래스. 약 1KB 이하의 작은 할당이 거의 락 없이 처리됨.
4.5 mmap Threshold
큰 할당 (기본 128KB+)은 arena를 거치지 않고 직접 mmap. 이유:
- arena 단편화 회피
free시 즉시 OS에 반환 (munmap)
문제: 매우 큰 할당이 자주 일어나면 mmap/munmap 비용이 큼. 동적으로 threshold가 조정된다 (M_MMAP_THRESHOLD 등).
4.6 ptmalloc의 한계
- 단편화: best-fit이 아니라 first-fit-like. 단편화가 잘 일어남.
- arena 락 경쟁: arena 수가 적으면 락 경합. 많으면 메모리 낭비.
- 메모리 반환 부진: 한 번 받은 메모리를 OS에 잘 안 돌려줌. RSS가 크게 부풀어 있음.
- 보안 취약점 표면: 30년 코드, 복잡한 로직. 매년 새 CVE.
이 한계 때문에 큰 워크로드에서는 ptmalloc을 jemalloc 또는 tcmalloc으로 교체하는 것이 흔하다.
# LD_PRELOAD로 jemalloc 사용
LD_PRELOAD=/usr/lib/libjemalloc.so ./my-app
5. jemalloc
5.1 디자인 철학
- 단편화 최소화가 최우선
- 멀티스레드 확장성: per-thread arena
- size class 전략: 정밀하게 분류된 사이즈 클래스
- 메모리를 OS에 잘 돌려줌: jemalloc은 사용 안 하는 페이지를 적극적으로 madvise
5.2 사이즈 클래스
jemalloc은 사이즈를 매우 정밀하게 분류한다. 예시 (64비트 시스템):
small: 8, 16, 32, 48, 64, 80, 96, 112, 128, 160, 192, 224, 256, ...
large: 4 KiB, 5 KiB, ..., 14 KiB
huge: 16 KiB, 20 KiB, 24 KiB, ..., 4 GiB
각 사이즈 클래스는 자기 free list를 가진다. 1.25배씩 차이나는 정교한 분류 — internal fragmentation이 최소화됨.
5.3 Region, Run, Chunk
jemalloc의 메모리 계층:
- Region: 사용자에게 주는 단위. 한 사이즈 클래스에 속함.
- Run: 같은 사이즈 클래스의 region들이 모인 페이지 그룹.
- Chunk: 여러 run을 담는 큰 메모리 영역 (보통 4MB).
[Chunk (4MB)]
[Run for 16-byte regions]
[region][region][region]...
[Run for 32-byte regions]
[region][region]...
[Run for 64-byte regions]
...
5.4 Per-Thread Arena
각 스레드가 자기 arena에 핀된다. 작은 할당은 자기 arena의 thread cache (tcache)에서 처리. 미스 시 arena로 가서 락을 잡음.
arena 수는 동적으로 조정된다. 기본은 4 * num_cpus 정도. CPU 친화성을 활용.
5.5 Background Threads
jemalloc은 백그라운드 스레드를 띄워서 사용 안 하는 메모리를 OS에 돌려주는 작업을 한다. 이는 RSS를 낮게 유지하는 데 결정적이다.
metadata.background_thread:true
5.6 Profiling
jemalloc은 강력한 메모리 프로파일링을 내장한다.
MALLOC_CONF=prof:true,lg_prof_sample:19,prof_prefix:./jeprof ./my-app
이는 매 약 512KB 할당마다 호출 스택 샘플을 기록한다. 끝에 jeprof 도구로 분석하면 어떤 함수가 가장 많은 메모리를 할당했는지 알 수 있다.
Facebook은 자기 인프라 디버깅에 이 기능을 광범위하게 사용한다.
5.7 단편화 회피의 미세함
jemalloc의 단편화 회피는 여러 기법의 조합이다:
- 정밀한 사이즈 클래스 — internal fragmentation 최소화
- 사이즈 클래스 격리 — 다른 사이즈는 다른 run에. external fragmentation 격리.
- Best-fit 내 사이즈 클래스 — run 안에서는 best-fit
- Run 단위 회수 — 한 run의 모든 region이 free되면 run을 통째로 회수
이 조합 덕분에 jemalloc은 ptmalloc보다 30-50% 적은 메모리를 쓰는 경우가 흔하다.
6. tcmalloc
6.1 핵심 아이디어
이름 그대로 — Thread-Caching Malloc. 각 스레드가 자기 캐시를 가지고, 작은 할당의 거의 100%가 캐시에서 처리. 락은 거의 안 잡힘.
6.2 세 계층 구조
tcmalloc의 메모리는 세 계층:
- Thread Cache: 스레드별. 작은 객체 (256KB 이하)의 free list.
- Central Free List: 글로벌. 사이즈별 free list. 락 보호.
- Page Heap: 큰 단위 (페이지) 관리. mmap에서 받은 큰 영역.
[Thread 1 cache] -- [Central Free List] -- [Page Heap] -- [OS mmap]
[Thread 2 cache] --
[Thread 3 cache] --
작은 할당은 thread cache → 미스면 central → 미스면 page heap → OS.
6.3 사이즈 클래스
tcmalloc도 사이즈 클래스 기반. 약 88개 사이즈 클래스. jemalloc만큼 정밀하지는 않지만 충분히 효율적.
6.4 Thread Cache 동작
// 의사 코드
void *malloc(size_t size) {
if (size > kMaxSize) {
// 큰 할당 — page heap으로 직접
return PageHeap::Alloc(size);
}
int cl = SizeClass(size);
void *ptr = thread_cache.list[cl].pop();
if (!ptr) {
// cache 미스 — central에서 batch로 가져옴
thread_cache.list[cl].refill_from_central(cl);
ptr = thread_cache.list[cl].pop();
}
return ptr;
}
핵심 트릭: cache 미스 시 한 번에 여러 객체 (예: 32개)를 가져온다. 다음 32번의 할당은 다시 락 없이 처리됨.
6.5 Cache 사이즈 동적 조정
스레드가 많은 할당을 하면 cache가 커지고, 적게 할당하면 줄어든다. 동적 조정 알고리즘. 또한 MallocExtension::ReleaseFreeMemory()로 명시적으로 줄일 수 있다.
6.6 TCMalloc-NG (Next Generation)
Google의 새 tcmalloc. 옛 tcmalloc과 호환 인터페이스이지만 내부는 새로 작성. 핵심 변화:
- Per-CPU 캐시 (per-thread 대신). CPU pinning과 함께 쓰면 더 효율적.
- Restartable sequences (rseq): 사용자 공간 atomic을 위한 새 syscall. cache 접근이 락 없이도 안전.
- 개선된 사이즈 클래스
GitHub의 google/tcmalloc 리포지토리가 이 새 버전이다. 옛 tcmalloc은 gperftools.
6.7 사용
LD_PRELOAD=/usr/lib/libtcmalloc.so ./my-app
또는 빌드 시 링크:
gcc app.c -o app -ltcmalloc
7. mimalloc
7.1 디자인 철학
Microsoft Research가 2019년 발표. "작고 빠른 할당자". 주요 목표:
- 작음: 10K LOC 이하 (대부분 할당자는 수십 K LOC)
- 빠름: 벤치마크에서 jemalloc/tcmalloc 비슷하거나 빠름
- 단순: 검토하기 쉬움
- 단편화 적음
7.2 Free List Sharding
mimalloc의 핵심 혁신. 전통적인 할당자는 한 사이즈 클래스에 한 free list를 둔다. mimalloc은 page 단위로 free list를 분리한다.
[Page A (16-byte slots)] → [free list A]
[Page B (16-byte slots)] → [free list B]
[Page C (16-byte slots)] → [free list C]
각 page에 자기 free list. 할당은 한 page의 free list에서. 그 page가 다 차면 다른 page로.
이 구조의 이점:
- Locality: 같은 page의 객체들이 함께 할당됨. 캐시 친화적.
- Free list가 짧음: 한 page의 슬롯 수만큼만. 검색이 빠름.
- Free 시 즉시 page에 반환: 다른 free list 검색 불필요.
7.3 Heartbeat
Mimalloc은 주기적으로 "heartbeat"를 실행한다 — 사용 안 하는 page를 OS에 돌려주고, 통계를 갱신하고, 단편화를 정리한다. 이는 작은 백그라운드 작업이고 거의 비용이 없다.
7.4 Free Delayed
Free된 객체는 즉시 page의 free list로 안 간다. 일단 thread-local "delayed free" 리스트에 들어간다. 주기적으로 batch로 page에 반환.
이는 cache 친화성을 높이고 (free 작업이 batch), 멀티스레드 시 다른 스레드의 할당과 충돌을 줄인다.
7.5 Sizes
mimalloc의 기본 page 사이즈: 64KB (작은 객체용). 4MB (중간 객체용). 1GB (큰 객체용).
작은 객체는 64KB page에 잘게 잘려서 들어가고, 중간/큰 객체는 더 큰 page를 통째로 차지하거나 나눠 쓴다.
7.6 안전성
mimalloc은 다음 검증을 옵션으로 켤 수 있다:
- Secure mode: free list pointer encoding, guard page, randomization
- Debug mode: double-free 감지, use-after-free 감지
이는 hardened allocator (다음 절)와 일부 겹친다.
7.7 사용
LD_PRELOAD=/usr/lib/libmimalloc.so ./my-app
또는:
gcc app.c -o app -lmimalloc
★ Insight ─────────────────────────────────────
- mimalloc의 free list sharding은 단순한 진보가 아니다: 전통적인 free list는 모든 page의 같은 사이즈 객체를 하나의 리스트에 묶었다. 이는 cache 미스를 자주 일으키고, free list가 길어진다. Page 단위 sharding은 이 문제를 통째로 회피한다. 단순하고 우아하다.
- 할당자 작은 것의 가치: 10K LOC 이하라는 사실은 검토와 보안 감사가 가능하다는 뜻이다. ptmalloc은 수십 K LOC인데 매년 새 CVE가 나온다. mimalloc 같은 작은 코드는 이론적으로 더 안전하다.
- Microsoft Research가 만든 게 의미 있다: Microsoft가 자기 인프라 (Windows, Office, SQL Server)에 적용할 수 있는 할당자가 필요했다. 그래서 학술적이지만 실용적인 결과. 이는 학계와 업계의 연결 모델로서 가치가 있다.
─────────────────────────────────────────────────
8. 벤치마크 — 어느 것이 가장 빠른가
8.1 절대적 답은 없다
워크로드에 따라 답이 다르다. 일반적 경향:
- 단일 스레드, 작은 할당: ptmalloc tcache, jemalloc, mimalloc 비슷. tcmalloc 약간 빠름.
- 멀티스레드, 작은 할당: tcmalloc, mimalloc, jemalloc이 ptmalloc을 크게 앞섬.
- 멀티스레드, 큰 할당: jemalloc이 단편화 회피로 메모리 효율 좋음.
- 드물게 있는 큰 할당: 차이 적음. mmap이 알아서 처리.
8.2 대표적 벤치마크
mstress benchmark (mimalloc 팀이 공개):
| Allocator | Time (s) | RSS (GB) |
|---|---|---|
| ptmalloc | 18.2 | 4.5 |
| jemalloc | 12.4 | 3.2 |
| tcmalloc | 11.8 | 3.5 |
| mimalloc | 10.9 | 3.1 |
(숫자는 대략적, 워크로드에 따라 다름. 출처: mimalloc 논문.)
Redis benchmark: Redis는 jemalloc을 디폴트로 쓴다. ptmalloc 대비 메모리가 약 40% 적게 든다고 알려져 있다. tcmalloc/mimalloc도 비슷한 결과.
8.3 latency tail
평균 시간보다 p99 latency가 더 중요한 경우:
| Allocator | mean (us) | p99 (us) | p99.9 (us) |
|---|---|---|---|
| ptmalloc | 0.8 | 5.2 | 28 |
| jemalloc | 0.7 | 3.1 | 12 |
| tcmalloc | 0.6 | 2.5 | 9 |
| mimalloc | 0.6 | 2.3 | 8 |
(역시 대략적 수치)
8.4 어떻게 측정하나
# 실제 워크로드로 비교
for alloc in libc jemalloc tcmalloc mimalloc; do
if [ "$alloc" = "libc" ]; then
./my-app
else
LD_PRELOAD=/usr/lib/lib${alloc}.so ./my-app
fi
done
벤치마크 워크로드는 자기 워크로드를 흉내내야 한다. 일반 벤치마크의 결과는 참고만.
9. 단편화 — 진짜 깊이 있게
9.1 두 종류의 단편화
- External fragmentation: free 영역이 작은 조각들로 흩어짐. 합은 충분하지만 큰 할당이 안 됨.
- Internal fragmentation: 할당된 chunk 안에 사용 안 되는 공간. 사이즈 클래스 반올림으로 발생.
9.2 External 단편화 측정
fragmentation = 1 - (largest_free_block / total_free_memory)
이 값이 높으면 단편화 심함. 0.9 이상이면 큰 할당이 거의 안 됨.
9.3 Internal 단편화 측정
internal_frag = (allocated_size - requested_size) / allocated_size
전체 할당된 메모리 중 사용 안 되는 비율. 보통 5-15%.
9.4 사이즈 클래스가 단편화에 미치는 영향
사이즈 클래스가 정밀할수록 internal fragmentation은 줄지만, 클래스 수가 많아져 메타데이터가 커진다. 적당한 균형이 필요.
jemalloc은 약 200개의 클래스 (8KB 미만에서). tcmalloc은 약 88개. mimalloc은 약 70개. ptmalloc fastbins는 10개 + smallbins 62개.
9.5 단편화의 측정 도구
- jemalloc:
MALLOC_CONF=stats_print:true— 종료 시 통계 출력 - tcmalloc:
MallocExtension::GetStats() - glibc malloc_stats(): ptmalloc의 통계
- valgrind massif: 시간에 따른 힙 사용량 그래프
9.6 단편화 회피 전략
- 객체 풀: 같은 사이즈 객체를 미리 할당해두고 재사용.
- arena per workload: 짧은 수명 객체와 긴 수명 객체를 다른 arena에.
- Bulk free: 한 번에 많은 객체를 free (할당자가 효율적으로 처리).
- Right-sizing: 정확한 사이즈로 요청 (사이즈 클래스 경계를 의식).
10. 멀티스레드 어렵게 만드는 것
10.1 락 경쟁
전통적인 글로벌 락 할당자는 N개의 스레드가 동시에 malloc하면 1/N의 처리량밖에 안 나온다. 이를 회피하는 것이 모든 모던 할당자의 목표.
10.2 False Sharing
같은 cache line에 두 스레드의 데이터가 있으면, 하나가 쓸 때마다 다른 하나의 cache line이 invalidate된다. 이는 매우 느리다.
할당자는 cache line 단위로 padding해서 false sharing을 방지한다. 예: thread cache의 메타데이터를 64바이트 정렬.
10.3 NUMA 인지
NUMA 시스템에서는 CPU와 메모리의 거리가 중요하다. 할당된 페이지는 어느 노드에 있을까?
- First-touch: 처음 접근한 CPU의 노드에 할당 (Linux 기본)
- Interleave: 모든 노드에 라운드로빈
jemalloc은 NUMA를 인지한다. 옵션 (opt.thp:always, opt.metadata_thp:always)으로 NUMA 친화적 할당 가능.
tcmalloc-NG도 per-CPU cache로 NUMA 친화성을 자연스럽게 가진다.
10.4 Cross-Thread Free
스레드 A가 할당한 메모리를 스레드 B가 free할 수 있을까? 가능하지만 까다롭다.
- 대안 1: B가 A의 cache에 직접 접근 — 락 필요
- 대안 2: B가 자기 cache에 임시 보관 — 나중에 A가 가져감
- 대안 3: 글로벌 free list로 보냄 — 락 필요하지만 짧음
mimalloc은 "deferred free" 메커니즘으로 처리. cross-thread free는 임시 큐에 들어가고 주기적으로 처리된다.
10.5 멀티스레드 벤치마크
스레드 수에 따른 처리량 (백만 op/s):
| Allocator | 1 thread | 8 threads | 64 threads |
|---|---|---|---|
| ptmalloc | 12 | 35 | 28 (락 경쟁) |
| jemalloc | 11 | 65 | 200 |
| tcmalloc | 12 | 70 | 250 |
| mimalloc | 13 | 75 | 280 |
ptmalloc은 8 스레드를 넘어서면 더 안 빨라진다 (오히려 느려짐). 다른 할당자들은 거의 선형 확장.
11. NUMA 인지 할당
11.1 왜 중요한가
Linux 메모리 글에서 봤듯, NUMA 시스템에서 원격 노드 액세스는 약 2배 느리다. 멀티 소켓 데이터베이스 서버에서는 결정적 차이.
11.2 First-Touch의 함정
Linux의 first-touch 정책은 "처음 접근한 CPU의 노드에 페이지 할당"이다. 이는 보통 좋다 — 그러나 할당자가 페이지를 미리 받아두면 깨진다.
예: 메인 스레드가 큰 arena를 할당. 나중에 worker 스레드들이 그 arena에서 chunk를 받아 씀. 그러면 모든 chunk가 메인 스레드의 노드에 있고, worker들은 원격 액세스.
11.3 해결책
- arena per CPU: 각 CPU에 자기 arena. CPU pinning과 함께. tcmalloc-NG가 이 방향.
- NUMA-aware mmap: mmap 시 명시적으로 노드 지정 (
mbind). - Touch-on-alloc: 할당된 chunk를 해당 스레드가 즉시 touch. first-touch가 옳은 노드를 선택.
11.4 jemalloc의 NUMA 옵션
MALLOC_CONF=arena_per_cpu:true ./my-app
이 옵션으로 각 CPU에 별도 arena가 만들어지고, 자동으로 first-touch가 옳게 동작.
12. ASAN과 메모리 디버깅
12.1 AddressSanitizer (ASAN)
LLVM/Clang에 들어 있는 메모리 에러 감지 도구. 컴파일 시 활성화:
clang -fsanitize=address -g app.c -o app
ASAN은 자기만의 할당자를 사용한다. 모든 할당된 영역 주변에 "redzone"을 둬서 buffer overflow를 즉시 감지. free된 영역은 한동안 quarantine에 보관해서 use-after-free를 감지.
오버헤드: 약 2배 (시간), 약 3배 (메모리). 그러나 개발 단계에서는 매우 가치 있음.
12.2 MemorySanitizer (MSAN)
초기화 안 된 메모리 읽기를 감지. ASAN과 다른 도구.
clang -fsanitize=memory -g app.c -o app
12.3 LeakSanitizer (LSAN)
메모리 누수 감지. ASAN의 일부로 자동 활성화.
12.4 Valgrind
ASAN보다 더 깊은 도구 (그러나 더 느림). 자기만의 가상 머신 위에서 프로그램을 실행. 모든 메모리 액세스를 검사.
valgrind --tool=memcheck ./app
valgrind는 컴파일 옵션 없이도 작동한다 (바이너리만 있어도). ASAN보다 10-50배 느리지만 더 자세하다.
12.5 Heaptrack
KDE 프로젝트의 메모리 프로파일러. 모든 할당을 기록하고 GUI로 분석.
heaptrack ./my-app
heaptrack_gui heaptrack.my-app.12345.gz
매우 직관적인 시각화 — flamegraph 형태로 어디서 메모리가 새는지 본다.
13. Hardened Allocators — 보안 우선
13.1 동기
메모리 할당자의 메타데이터는 매력적인 공격 표면이다. heap exploit의 상당 부분이 할당자 메타데이터 손상에서 시작한다 (예: house of force, fastbin attack).
Hardened allocator는 보안을 처음부터 설계 목표로 삼는다. 약간의 성능을 희생해서 공격을 어렵게 만든다.
13.2 보안 기법
- Pointer encoding: free list pointer를 secret key로 XOR. 단순한 leak으로는 follow 불가.
- Guard pages: 큰 할당의 양 끝에 read-only 페이지. overflow가 즉시 segfault.
- Randomization: 같은 사이즈의 할당이 매번 다른 위치에 가도록.
- Quarantine: free된 메모리를 즉시 재사용 안 함. use-after-free 회피.
- Double-free detection: 같은 포인터를 두 번 free하면 abort.
13.3 대표 hardened allocators
- scudo: LLVM의 hardened allocator. Android에서 기본.
- OpenBSD malloc: 보안 우선.
- GrapheneOS hardened malloc: 모바일 보안.
- mimalloc secure mode: mimalloc의 보안 옵션.
13.4 비용
Hardened allocator는 보통 일반 할당자보다 10-30% 느리다. 메모리도 약간 더 쓴다. 그러나 보안이 중요한 환경 (브라우저, OS, 보안 critical 앱)에서는 가치 있다.
13.5 PartitionAlloc — Chrome의 선택
Chrome 브라우저는 자기만의 할당자 PartitionAlloc을 만들었다. 핵심:
- 타입별 partition: HTML 노드, JavaScript 객체, 이미지 등이 다른 partition에 살음. 한 타입의 exploit이 다른 타입을 손상시키지 못함.
- 사이즈 + 타입:
malloc(64)의 결과가 어떤 타입인지에 따라 다른 위치 - MiraclePtr (Backup Ref Ptr): use-after-free를 감지하는 reference counting
Chrome은 이를 광범위하게 적용해서 memory safety bug 수를 크게 줄였다.
14. Slab과 Object Pool
14.1 객체 풀의 가치
같은 사이즈의 객체가 자주 만들어지고 해제되는 경우, 일반 할당자보다 객체 풀이 훨씬 빠르다.
typedef struct ObjectPool {
void *free_list;
size_t object_size;
} ObjectPool;
void *pool_alloc(ObjectPool *p) {
if (!p->free_list) {
// 새 batch 할당
char *block = malloc(p->object_size * 64);
for (int i = 0; i < 64; i++) {
void **slot = (void **)(block + i * p->object_size);
*slot = p->free_list;
p->free_list = slot;
}
}
void *obj = p->free_list;
p->free_list = *(void **)obj;
return obj;
}
void pool_free(ObjectPool *p, void *obj) {
*(void **)obj = p->free_list;
p->free_list = obj;
}
매 할당이 두세 줄의 인스트럭션. malloc/free의 50-100배 빠르다.
14.2 어디에 쓰이나
- 네트워크 패킷 버퍼
- HTTP 요청 객체
- 데이터베이스 row buffer
- 게임 entity
거의 모든 고성능 인프라가 어떤 형태의 객체 풀을 가진다.
14.3 Arena 할당
다른 패턴: 짧은 수명의 많은 객체를 한 arena에 할당하고, 끝에 통째로 free. 매 객체 free가 거의 무료.
typedef struct Arena {
char *base;
size_t pos;
size_t cap;
} Arena;
void *arena_alloc(Arena *a, size_t size) {
void *ptr = a->base + a->pos;
a->pos += size;
return ptr;
}
void arena_reset(Arena *a) {
a->pos = 0; // 모든 객체 무효, 메모리 재사용
}
컴파일러, 인터프리터, request-scoped 워크로드 (HTTP 핸들러)에 매우 적합.
15. Rust의 alloc API
15.1 글로벌 할당자
Rust는 #[global_allocator] 어트리뷰트로 글로벌 할당자를 지정할 수 있다.
use jemallocator::Jemalloc;
#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc;
fn main() {
let v = vec![1, 2, 3];
}
기본은 시스템 malloc (Linux에서는 ptmalloc). jemallocator, tcmalloc-rs, mimalloc 같은 crate로 교체 가능.
15.2 GlobalAlloc trait
unsafe trait GlobalAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8;
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout);
}
이 trait을 구현하면 자기만의 할당자를 만들 수 있다.
15.3 Allocator trait (nightly)
Allocator trait이 있는 nightly Rust에서는 컬렉션마다 다른 할당자를 쓸 수 있다.
let mut v: Vec<i32, MyAllocator> = Vec::new_in(MyAllocator);
이는 C++의 std::allocator와 비슷한 모델. 아직 stabilize 안 됨.
15.4 bumpalo — Rust용 arena
use bumpalo::Bump;
let arena = Bump::new();
let x = arena.alloc(42);
let y = arena.alloc("hello");
// arena가 drop될 때 모두 free
매우 빠른 짧은 수명 할당. 컴파일러 같은 워크로드에 자주 쓰임.
16. 사례 — Redis와 jemalloc
16.1 왜 Redis가 jemalloc인가
Redis는 키-값 데이터를 모두 메모리에 보관한다. 메모리 효율이 곧 비용이다.
Redis 팀은 처음에 ptmalloc을 썼지만, 운영 환경에서 RSS가 데이터 크기보다 훨씬 크다는 것을 발견했다 — 단편화 때문이다. jemalloc으로 바꾸자 RSS가 약 30% 줄었다.
이후 Redis는 jemalloc을 디폴트로 묶어서 배포한다 (make MALLOC=jemalloc). 옵션으로 libc malloc도 지원하지만, 운영 환경에서 그렇게 쓰는 곳은 거의 없다.
16.2 Redis 구성
Redis 빌드 시:
make MALLOC=jemalloc # 기본
make MALLOC=tcmalloc # 대안
make MALLOC=libc # 시스템 malloc
운영 환경에서는 거의 항상 jemalloc.
16.3 메모리 통계
Redis는 jemalloc과 통합되어 메모리 통계를 노출한다:
INFO memory
used_memory:12345678
used_memory_rss:14567890
mem_fragmentation_ratio:1.18
mem_allocator:jemalloc-5.3.0
mem_fragmentation_ratio가 1.5 이상이면 단편화 심함. Redis 4.0부터 active defragmentation 기능이 있어서 자동으로 잠재적 fragmentation을 정리한다.
17. 사례 — Firefox와 mozjemalloc
17.1 역사
Firefox 3 시절, Mozilla는 메모리 사용량이 폭증하는 문제를 겪었다. 조사 결과 ptmalloc의 단편화가 주요 원인. jemalloc으로 교체하고 일부 Firefox 특화 수정을 가해 mozjemalloc을 만들었다.
이는 jemalloc 채택의 첫 메이저 사례였고, 이후 Facebook이 따랐다.
17.2 무엇이 다른가
mozjemalloc은 일반 jemalloc과 거의 같지만 일부 차이:
- Firefox 특화 사이즈 클래스
- Memory pressure 콜백 (low memory 시 회수)
- 디버깅 통합
Firefox는 자기 할당자를 지속적으로 개선한다. 매 메이저 버전마다 메모리 사용량 개선이 release note에 등장.
18. 사례 — Chrome과 PartitionAlloc
18.1 보안 우선 설계
Chrome은 거대한 공격 표면이다 (모든 웹사이트가 잠재적 공격자). 메모리 안전성이 절대 우선.
PartitionAlloc은 이 요구에 답한다:
- 타입별 partition 격리
- Backup Ref Pointer (BRP) — pointer aliasing 감지
- Guard page 사용
- Randomization
18.2 BackUp RefPtr (MiraclePtr)
각 raw pointer가 reference count로 추적된다. use-after-free 시 해당 메모리는 zero로 채워지고, 다음 액세스가 안전한 segfault로 이어진다 (exploit 불가).
이는 약간의 성능 비용이지만 (5-10%), Chrome의 위협 모델에서는 가치 있다.
18.3 결과
PartitionAlloc + BRP 적용 후 Chrome의 use-after-free 관련 보안 버그가 크게 감소했다. Google Project Zero의 통계에서 확인 가능.
19. 환경 변수와 튜닝
19.1 ptmalloc
MALLOC_ARENA_MAX=2 ./my-app # arena 수 제한
MALLOC_TRIM_THRESHOLD_=131072 ./my-app # OS 반환 임계값
MALLOC_MMAP_THRESHOLD_=131072 ./my-app # mmap 임계값
MALLOC_CHECK_=3 ./my-app # debug mode
19.2 jemalloc
# 통계 출력
MALLOC_CONF=stats_print:true ./my-app
# 프로파일링
MALLOC_CONF=prof:true,lg_prof_sample:19 ./my-app
# Background 스레드
MALLOC_CONF=background_thread:true ./my-app
# Decay time (메모리를 OS에 돌려주는 속도)
MALLOC_CONF=dirty_decay_ms:5000 ./my-app
19.3 tcmalloc
TCMALLOC_RELEASE_RATE=10 ./my-app # 메모리 반환 속도
TCMALLOC_MAX_TOTAL_THREAD_CACHE_BYTES=... # thread cache 한도
19.4 mimalloc
MIMALLOC_VERBOSE=1 ./my-app # 통계 출력
MIMALLOC_LARGE_OS_PAGES=1 ./my-app # huge page 사용
MIMALLOC_PURGE_DELAY=5000 ./my-app # 메모리 반환 지연
20. 흔한 문제와 디버깅
20.1 "RSS가 예상보다 큼"
원인:
- ptmalloc이 메모리를 OS에 안 돌려줌
- 단편화로 인해 free 영역이 sparse
- 진짜 누수
해결:
- 다른 할당자 시도 (jemalloc의 background thread가 적극적으로 반환)
MALLOC_TRIM_THRESHOLD_낮추기malloc_trim(0)명시적 호출- 진짜 누수면 valgrind/ASAN
20.2 "lock contention 의심"
원인:
- ptmalloc이 arena 락을 자주 잡음
해결:
MALLOC_ARENA_MAX를 늘림- 또는 jemalloc/tcmalloc/mimalloc으로 바꿈
20.3 "단편화로 OOM"
원인:
- 큰 할당이 안 됨 (외부 단편화)
해결:
- 객체 풀로 사이즈 균질화
- jemalloc으로 (단편화에 강함)
- arena 단위 할당 패턴 (요청별 arena, 끝에 reset)
20.4 "double free"
원인:
- 같은 포인터를 두 번 free
해결:
- ASAN으로 잡기
- free 후 NULL 대입 습관화
- C++의 unique_ptr/shared_ptr 사용
- Rust 채택 (컴파일 타임에 잡힘)
20.5 "use-after-free"
원인:
- free된 메모리에 접근
해결:
- ASAN
- hardened allocator (mimalloc secure, scudo)
- valgrind
21. 미래 — 메모리 할당자의 다음 단계
21.1 NUMA 깊이
CXL, persistent memory 등 새 메모리 계층이 등장. 할당자가 어떤 메모리를 어떤 객체에 줄지 결정하는 것이 중요해진다.
21.2 보안 강화
브라우저, OS, 보안 critical 앱에서 hardened allocator의 채택이 늘어난다. PartitionAlloc 모델이 표준이 될 가능성.
21.3 GC와의 통합
일부 시스템은 GC와 malloc을 함께 쓴다. 두 시스템이 서로 알게 만드는 것이 새 도전.
21.4 ML 기반 사이즈 예측
객체의 수명을 예측해서 적절한 영역에 할당하는 ML 기반 할당자 연구가 있다. 아직 실험적.
21.5 Restartable Sequences (rseq)
Linux 4.18+ syscall. 사용자 공간의 atomic operation을 가능하게 한다. tcmalloc-NG가 활용. 다른 할당자도 채택할 가능성.
22. 결론 — 할당자는 끝나지 않는다
이 글을 다 읽었다면 다음 질문에 답할 수 있을 것이다:
- malloc이 내부적으로 무엇을 하나?
- ptmalloc, jemalloc, tcmalloc, mimalloc의 차이는?
- Bin이 무엇이고 왜 필요한가?
- Thread cache가 어떻게 락 경쟁을 회피하나?
- 단편화의 두 종류와 회피 방법은?
- NUMA 시스템에서 할당자가 신경 써야 할 것은?
- Hardened allocator는 무엇을 다르게 하나?
- 어떤 도구로 메모리 문제를 디버깅하나?
할당자는 보이지 않지만 어디에나 있다. 잘 동작할 때는 신경 쓸 일이 없지만, 한 번 문제가 생기면 시스템 전체의 성능과 안정성을 좌우한다. 이 글이 그 보이지 않는 층을 조금 더 잘 보게 해줬기를 바란다.
이 글로 메모리 계층의 트릴로지가 완성되었다:
- Linux 메모리 관리 — 커널이 페이지를 어떻게 관리하나
- 가비지 컬렉션 — 런타임이 객체를 어떻게 관리하나
- 이 글 — 사용자 공간 할당자가 raw 바이트를 어떻게 관리하나
세 글이 모이면 "내 객체가 어디 사는지, 어떻게 거기 가는지, 언제 떠나는지"의 모든 단계에 답할 수 있다. mmap 한 syscall에서 시작해 jemalloc의 size class를 거쳐 GC의 minor cycle까지 — 이것이 모던 시스템 메모리의 풍경이다.
다음 글에서는 [Rust ownership과 borrow checker]를 다룰 예정이다. GC와 할당자 외에 메모리를 안전하게 다루는 또 다른 길이 있다.
부록 A — 참고 자료
- Doug Lea, "A Memory Allocator" — dlmalloc 원 설명.
- jemalloc 공식 문서 — jemalloc 매뉴얼 페이지.
- Jason Evans, "A Scalable Concurrent malloc(3) Implementation for FreeBSD" — jemalloc 원 논문.
- Google tcmalloc design doc — tcmalloc 디자인.
- Daan Leijen et al., "Mimalloc: Free List Sharding in Action" — mimalloc 원 논문.
- Wilson, Johnstone, Neely, Boles, "Dynamic Storage Allocation: A Survey and Critical Review" — 1995년의 종합 서베이, 여전히 좋은 입문.
- PartitionAlloc 설명 — Chrome의 할당자.
- Michael McKenney, "What Every Programmer Should Know About Memory Allocators" — 좋은 입문.
- scudo allocator — LLVM의 hardened.
- Heaptrack — 메모리 프로파일러.
부록 B — 자주 묻는 질문
Q: 어떤 할당자가 가장 좋나? A: 워크로드에 따라 다르다. 일반적으로:
- 일반 서버: jemalloc (RSS 효율)
- 처리량 우선: tcmalloc
- 작고 빠르게: mimalloc
- 보안: scudo, PartitionAlloc
Q: ptmalloc은 정말 안 좋나? A: "안 좋다"보다는 "오래되고 멀티스레드 확장성이 약하다." 단일 스레드, 작은 워크로드에는 충분.
Q: LD_PRELOAD로 할당자를 바꿔도 안전한가? A: 보통 안전. 그러나 일부 라이브러리는 자기 할당자를 가정하므로 호환성 이슈가 있을 수 있다. 항상 테스트 필요.
Q: Firefox와 Chrome은 왜 자기 할당자를 만들었나? A: 일반 할당자가 자기 워크로드에 최적이 아니어서. 또한 보안 요구 사항이 매우 높아서.
Q: jemalloc의 메모리 통계를 어떻게 보나?
A: MALLOC_CONF=stats_print:true로 종료 시 출력. 또는 프로그램 안에서 mallctl("stats.allocated", ...)로 런타임 조회.
Q: 작은 객체를 자주 만드는데 빠르게 하려면? A: 객체 풀. 또는 arena 할당. malloc/free의 50-100배 빠르다.
Q: Rust에서도 할당자가 필요한가?
A: 그렇다. Rust도 내부적으로 malloc-like 함수를 호출한다. #[global_allocator]로 교체 가능. jemallocator, mimalloc 등 crate가 있다.
Q: Valgrind와 ASAN 중 어느 것이 좋나? A: ASAN이 빠르고 (2배 정도), valgrind가 더 자세하다 (그러나 10-50배 느림). 개발 중에는 ASAN, 깊은 디버깅에는 valgrind.
부록 C — 미니 용어집
- malloc: 메모리 할당 함수.
- free: 메모리 해제 함수.
- chunk: 할당자가 사용자에게 주는 메모리 단위.
- header: chunk 메타데이터.
- footer: chunk 끝 메타데이터 (boundary tag).
- bin: 같은 사이즈의 free chunk 리스트.
- arena: 독립적인 chunk 풀과 락.
- fastbin (ptmalloc): 작은 사이즈의 LIFO bin.
- smallbin (ptmalloc): 작은 사이즈 bin.
- largebin (ptmalloc): 큰 사이즈 bin.
- unsorted bin (ptmalloc): 정렬 안 된 임시 bin.
- tcache (ptmalloc 2.26+): per-thread cache.
- size class: 사이즈별 분류.
- slab: 같은 사이즈 객체의 풀.
- first-fit / best-fit: free list 탐색 전략.
- coalescing: 인접 free chunk 합치기.
- internal fragmentation: chunk 안의 사용 안 된 공간.
- external fragmentation: free 영역의 sparse 분포.
- mmap threshold: 큰 할당을 mmap으로 처리하는 기준.
- thread cache: 스레드별 free list (락 회피).
- central free list: 글로벌 free list (락 보호).
- page heap: 페이지 단위 메모리 관리.
- NUMA: Non-Uniform Memory Access.
- hardened allocator: 보안 강화 할당자.
- PartitionAlloc: Chrome의 할당자.
- MiraclePtr: Chrome의 use-after-free 감지.
- scudo: LLVM의 hardened 할당자.
- dlmalloc: Doug Lea의 원 malloc.
- ptmalloc: glibc의 malloc.
- jemalloc: FreeBSD/Facebook의 malloc.
- tcmalloc: Google의 malloc.
- mimalloc: Microsoft Research의 malloc.
- rseq: Restartable sequences. 사용자 공간 atomic.
- ASAN: AddressSanitizer.
- MSAN: MemorySanitizer.
- LSAN: LeakSanitizer.
- valgrind: 메모리 디버깅 도구.
이 글로 메모리 트릴로지가 완성되었다. Linux 메모리 관리, 가비지 컬렉션, 그리고 이 글이 셋이 모이면 메모리 계층의 풍경이 그려진다 — 페이지에서 사이즈 클래스를 거쳐 GC 사이클까지. 다음 글에서는 Rust의 ownership과 borrow checker 같은 다른 길을 둘러볼 것이다.
현재 단락 (1/549)
`malloc`을 호출해본 모든 사람이 메모리 할당자를 사용해본 셈이다. 그러나 그 안에서 무슨 일이 일어나는지 아는 사람은 드물다. C 코드의 `malloc(1024)` 한 줄이...