Memory Architecture

REIN·2025년 12월 20일

게임 개발 초급 CS

목록 보기
4/18

메모리 계층의 전체 그림

동일한 단어가 여러 계층에서 등장하는 것을 깨달았을 것이다.

  • "캐시"라는 단어가 CPU 캐시, TLS 캐시, Thread Cache 등 여러 곳에서 등장
  • "메모리"가 RAM, 가상 메모리, 할당자 내부 등 모든 곳에 존재
  • 할당자의 진화가 하드웨어 변화 때문인지, 소프트웨어적 혁신인지 불명확

이 문서는 전체 그림을 그려서 각 개념이 어디에 위치하고, 왜 존재하는지 설명한다.


1. 메모리 계층 전체 구조

1.1 하드웨어에서 애플리케이션까지

계층구성 요소역할관리 단위속도
5. 애플리케이션new, malloc(), std::vector객체 생성/소멸객체-
4. 메모리 할당자jemalloc, tcmalloc효율적 메모리 분배바이트~MB~50ns
Thread Cache, Arena, Span
3. OS 커널Virtual Memory가상→물리 매핑4KB 페이지~1μs
mmap(), Page Table, TLB
2. 물리 메모리DRAM실제 데이터 저장바이트~100ns
1. CPU 캐시L1/L2/L3 SRAM빠른 접근 제공64B 캐시라인~1-10ns

1.2 데이터가 흐르는 경로

int* p = new int;를 호출하면:

단계계층동작
1애플리케이션new int 호출
2할당자Thread Cache에서 16바이트 블록 반환
3할당자(캐시 미스 시) Arena에서 Span 할당
4할당자(메모리 부족 시) OS에 mmap() 요청
5OS가상 주소 공간 예약 (아직 물리 메모리 할당 안 함!)
6애플리케이션*p = 42; (첫 접근)
7CPU/MMU가상→물리 변환 시도 → Page Fault 발생!
8OS물리 페이지 할당, Page Table 업데이트
9CPUDRAM에서 읽어 캐시에 로드, 연산 수행

핵심 인사이트: malloc()은 물리 메모리를 할당하지 않는다. 첫 접근 시 OS가 할당한다.


2. "캐시"의 세 가지 의미

"캐시"라는 단어가 혼란스러운 이유: 서로 다른 세 가지 의미로 사용된다.

2.1 CPU 캐시 (하드웨어)

구조:

위치캐시크기특징
코어별L132KB/core가장 빠름 (~1ns)
코어별L2256KB/core
공유L38-32MB전체 코어 공유
외부DRAM8-128GB가장 느림 (~100ns)

특징:

  • 하드웨어가 자동 관리 (프로그래머 제어 불가)
  • 목적: DRAM 접근 속도 숨기기 (100ns → 1ns)
  • 단위: 캐시 라인 (64 bytes)

왜 중요한가:

// 나쁜 예: 캐시 라인 낭비
struct Bad {
    int id;           // 4 bytes
    char padding[60]; // 60 bytes (의도치 않은 패딩)
    int value;        // 4 bytes ← 다른 캐시 라인!
};

// 좋은 예: 캐시 라인 효율적 사용
struct Good {
    int id;           // 4 bytes
    int value;        // 4 bytes ← 같은 캐시 라인
};

2.2 TLS(Thread Local Storage) 캐시 (OS/언어 런타임)

// TLS 변수 선언
thread_local int counter = 0;  // 각 스레드가 자신만의 복사본 가짐

void increment() {
    counter++;  // 다른 스레드와 충돌 없음
}

메모리 구조:

Thread 0Thread 1Thread 2
TLS 영역TLS 영역TLS 영역
counter: 5counter: 3counter: 8
errno: 0errno: 2errno: 0

특징:

  • 각 스레드가 독립된 복사본 가짐
  • 목적: 락 없이 스레드별 데이터 관리
  • 구현: FS/GS 세그먼트 레지스터 (x86-64)

2.3 할당자 캐시 (소프트웨어)

jemalloc의 Thread Cache, tcmalloc의 ThreadCache:

Thread Cache 구조 (TLS에 저장):

Size Class상태설명
8B[■][■][□][□][□]2개 할당됨, 3개 여유
16B[■][□][□][□][□]1개 할당됨
32B[■][■][■][□][□]3개 할당됨
64B[□][□][□][□][□]모두 여유

특징:

  • 소프트웨어가 관리하는 객체 풀
  • 목적: 락 경합 없이 빠른 할당/해제
  • 구현: TLS를 사용하여 스레드별 캐시 유지

2.4 세 가지 "캐시" 비교

구분CPU 캐시TLS할당자 캐시
관리 주체하드웨어OS/런타임할당자 라이브러리
목적DRAM 지연 숨기기스레드별 데이터락 없는 할당
단위64B 캐시라인변수 단위객체 단위
제어 가능불가 (힌트만)가능가능
위치CPU 칩 내부각 스레드 스택 근처

3. Virtual Memory: 왜 필요한가

3.1 가상 메모리가 없던 시절

1980년대 초기 시스템:

프로그램 A: "나는 주소 0x1000에 데이터를 저장할 거야"
프로그램 B: "나도 0x1000에 저장해야 하는데..."
결과: 충돌! 데이터 덮어쓰기!

문제점:
1. 프로그램들이 물리 주소 직접 사용 → 충돌
2. 프로그램 크기 > 물리 메모리 → 실행 불가
3. 메모리 보호 없음 → 버그가 전체 시스템 크래시

3.2 가상 메모리의 해결책

프로그램 A프로그램 B물리 메모리
가상 0x1000 →물리 0x5000: A의 데이터
가상 0x2000 →
가상 0x1000 →물리 0x8000: B의 데이터

핵심 개념:

  • 각 프로세스는 독립된 가상 주소 공간을 가짐
  • 같은 가상 주소 0x1000이 다른 물리 주소로 매핑
  • Page Table이 가상→물리 변환 담당

3.3 Page Table과 TLB

주소 변환 과정:

단계동작
1가상 주소 0x12345678
2Page Number: 0x12345, Offset: 0x678
3Page Table 조회: 0x12345 → 0x7ABCD
4물리 주소: 0x7ABCD678

문제: Page Table 조회가 느리다

메모리 접근 시마다 Page Table 조회 → 2배 느려짐!

해결: TLB (Translation Lookaside Buffer)

구성 요소역할속도
TLB (CPU 내부 캐시)가상→물리 매핑 캐시1 사이클
Page Table (메모리)전체 매핑 저장100+ 사이클

할당자와의 연결:

  • HugePage (2MB): 더 적은 TLB 엔트리로 더 많은 메모리 커버
  • tcmalloc의 HugePageAwareAllocator: TLB 미스 최소화

3.4 Demand Paging

// 이 시점에는 물리 메모리 할당 안 됨!
void* p = mmap(NULL, 1GB, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

// 첫 접근 시 Page Fault → 그때 물리 페이지 할당
p[0] = 42;  // Page Fault! → OS가 물리 페이지 할당

왜 이렇게 하는가:

  • 대부분의 할당된 메모리는 즉시 사용되지 않음
  • 필요할 때만 물리 메모리 할당 → 효율적

4. 할당자 진화의 역사: 하드웨어가 바뀌었다

4.1 1990년대: 단일 코어 시대

항목내용
CPU단일 코어
메모리수십 MB
malloc 구현단순 free list
락 경합없음 (코어가 하나)
주요 문제단편화

당시 malloc:

static struct block* free_list;
static pthread_mutex_t lock;  // 하나의 전역 락

void* malloc(size_t size) {
    pthread_mutex_lock(&lock);  // 단일 코어라 거의 경합 없음
    // free_list에서 맞는 블록 찾기
    pthread_mutex_unlock(&lock);
}

4.2 2000년대: 멀티코어 등장

항목내용
CPU2-4 코어
메모리수 GB
문제전역 락 = 병목!
4개 코어가 malloc() 호출 → 3개는 대기

문제 발생:

Thread 0: malloc() → lock() → [작업] → unlock()
Thread 1: malloc() → lock() → [대기...대기...] → [작업]
Thread 2: malloc() → lock() → [대기...대기...대기...] → [작업]

해결: tcmalloc 등장 (2005, Google)

구조역할
Thread Cache (코어별)락 없이 빠른 할당
Central Heap (공유)가끔만 접근 (락 필요)

성능 향상:

  • 대부분의 할당: Thread Cache에서 처리 (락 없음!)
  • 캐시 미스 시에만 Central Heap 접근

4.3 2010년대: NUMA와 대용량 메모리

항목내용
CPU8-16 코어, NUMA 아키텍처
메모리64-128GB, 노드별 분리
새로운 문제Node 0 스레드 → Node 1 메모리 = 느림
메모리 128GB+ → Page Table 거대화 → TLB 미스

NUMA 구조:

NUMA Node 0NUMA Node 1
Core 0-7Core 8-15
Local DRAM 64GBLocal DRAM 64GB
↔ 느린 연결 ↔

jemalloc의 대응:

  • Arena per CPU: NUMA 노드별로 메모리 관리
  • Extent 기반 관리: 대용량 메모리 효율적 처리
  • Decay 기반 반환: 장기 실행 서버의 메모리 누수 방지

4.4 현재: 코어 수 폭증과 HugePage

항목내용
CPU64+ 코어
새로운 문제64개 스레드 × Thread Cache = 메모리 낭비
스레드가 코어 간 이동 → 이전 Thread Cache 접근 = 원격

Per-CPU Cache의 장점:

방식동작
Per-Thread (기존)Thread 0 → Core 0 → Core 3 이동 시, Cache는 Core 0 근처 (원격!)
Per-CPU (현재)Thread 0 → Core 0 → Core 3 이동 시, Core 3 Cache 사용 (로컬!)

tcmalloc의 대응:

  • Per-CPU Cache: 스레드가 아닌 코어 단위 캐시
  • HugePageAwareAllocator: 2MB 페이지로 TLB 효율 향상

4.5 진화 요약

시대하드웨어 변화문제해결책
1990s단일 코어단편화Best-fit, Buddy system
2000s멀티코어락 경합Thread Cache (tcmalloc)
2010sNUMA, 대용량원격 접근, TLB 미스Arena per CPU, Extent
2020s수십 코어캐시 낭비, 스레드 이동Per-CPU Cache, HugePage

결론: 할당자 진화는 하드웨어 변화에 대한 대응이다.


5. 모든 것의 연결: 실제 할당 흐름

5.1 malloc(64) 호출 시 전체 경로

단계계층동작조건
1애플리케이션malloc(64) 호출
2jemallocThread Cache 확인
Size class: 64B
→ 여유 있음: 즉시 반환 (~20ns)95% 여기서 끝
→ 없음: 다음 단계
3jemallocArena에서 Slab 할당캐시 미스
Bitmap에서 빈 슬롯 찾기 (O(1))
→ 반환 (~100ns)~4%
4jemallocExtent 할당 요청Slab 고갈
Retained extents 확인
→ 재사용 또는 OS 요청~0.9%
5OSmmap() 호출extent 없음
가상 주소 예약 (물리 할당 X)~0.1%
6애플리케이션*ptr = 42; (첫 쓰기)
7MMUPage Fault 발생!
8OS 커널물리 페이지 할당
Page Table, TLB 업데이트
9CPUDRAM → L1 캐시 로드 → 쓰기 완료

5.2 각 단계의 지연 시간

단계소요 시간빈도
Thread Cache 히트~20ns95%+
Arena 할당~100ns~4%
Extent 할당 (캐시됨)~500ns~0.9%
mmap() + Page Fault~10,000ns~0.1%

핵심: 대부분의 할당은 Thread Cache에서 끝난다.


6. 용어 정리

용어의미위치
CPU 캐시L1/L2/L3 SRAMCPU 칩 내부 (하드웨어)
TLSThread Local Storage각 스레드의 스택 근처 (OS 관리)
Thread Cache할당자의 스레드별 객체 풀TLS에 저장된 소프트웨어 구조
Arena할당자의 메모리 풀힙 (여러 스레드 공유 가능)
Extent/SpanOS에서 받은 큰 메모리 덩어리힙 (2MB 단위)
가상 메모리프로세스별 주소 공간OS가 관리하는 추상화
물리 메모리실제 DRAM하드웨어
Page Table가상→물리 매핑 테이블OS 커널 내 자료구조
TLBPage Table 캐시CPU 내부 (하드웨어)
HugePage2MB 크기 페이지OS/하드웨어 기능

마치며

Part 2가 다루는 내용은 소프트웨어 계층(할당자)이지만, 이 계층은 하드웨어 변화에 대응하며 진화해왔다:

  1. 멀티코어 → Thread Cache (락 회피)
  2. NUMA → Arena per CPU (로컬 메모리)
  3. 대용량 메모리 → Extent, Decay (효율적 관리)
  4. 많은 코어 → Per-CPU Cache, HugePage (TLB 효율)

"메모리"라는 단어가 혼란스러웠다면:

  • 물리 메모리: 실제 DRAM 칩
  • 가상 메모리: OS가 제공하는 추상화
  • 캐시 메모리: CPU 내 SRAM
  • 할당자 캐시: 소프트웨어가 관리하는 객체 풀

각각 다른 계층에서 다른 목적으로 사용되며, 현대 할당자는 이 모든 계층을 이해하고 최적화한다.

profile
RL Researcher, Video Game Developer

0개의 댓글