CPU 캐시

L1/L2/L3 캐시 계층과 캐시 라인

CPU는 RAM 접근이 느려서 L1/L2/L3 캐시를 계층적으로 두고 자주 쓰는 데이터를 가까이 둔다. 데이터를 가져올 때는 요청한 바이트만이 아니라 64바이트(캐시 라인) 단위로 주변까지 함께 가져오며, 이는 가까운 데이터를 곧 쓸 확률이 높다는 공간적 지역성에 기반한다. 멀티스레드에서는 서로 다른 변수가 같은 캐시 라인에 있으면 false sharing으로 성능이 떨어질 수 있다.

Memory

개념

CPU가 RAM에서 데이터를 읽으려면 ~100ns가 걸린다. CPU 클럭이 3GHz면 1ns에 명령어 3개를 처리할 수 있는데, RAM 한 번 읽는 동안 300개를 처리할 수 있는 시간을 기다리는 셈이다. 그래서 CPU 안에 작고 빠른 메모리를 여러 단계로 둔다.

캐시 크기 속도 위치
L1 ~64KB ~1ns 코어 안
L2 ~256KB ~4ns 코어 안
L3 ~8MB ~10ns 코어들이 공유
RAM ~16GB ~100ns CPU 밖

위로 갈수록 작고 빠르고, 아래로 갈수록 크고 느리다. CPU가 데이터를 읽을 때 L1부터 찾고, 없으면 L2, L3, 최종적으로 RAM까지 내려간다.

TLB가 주소 번역을 캐싱하는 거라면, L1/L2/L3는 데이터 자체를 캐싱하는 것이다.

캐시 라인

CPU가 메모리를 읽을 때 요청한 바이트만 가져오지 않는다. 주변 데이터까지 묶어서 64바이트(캐시 라인) 단위로 가져온다.

int arr[100];
// arr[0]을 읽으면 → arr[0]~arr[15]까지 64바이트를 통째로 캐시에 올림

arr[0] 다음엔 arr[1], arr[2]를 읽을 확률이 높다. 그때마다 RAM까지 내려가면 느리니까 한 번에 묶어서 가져오는 것이다. 이것을 공간적 지역성(spatial locality) 이라고 한다. "가까이 있는 데이터는 곧 쓰일 가능성이 높다"는 경험 법칙이며, TLB, 스왑, 캐시 라인 모두 같은 원리에 기반한다.

False Sharing

두 스레드가 서로 다른 변수를 쓰는데, 그 변수들이 같은 캐시 라인 안에 있으면 성능 문제가 생긴다.

캐시 라인 64바이트: [... 변수A ... 변수B ...]

스레드 1 → 변수A에 쓰기
스레드 2 → 변수B에 쓰기
  1. 스레드 1, 2 둘 다 같은 캐시 라인을 각자 캐시에 들고 있음
  2. 스레드 1이 변수A를 수정 → 스레드 2의 캐시 라인이 무효화됨
  3. 스레드 2가 변수B를 쓰려면 최신 캐시 라인을 다시 가져와야 함
  4. 스레드 2가 변수B를 수정 → 스레드 1의 캐시 라인이 무효화됨
  5. 이 과정이 작업할 때마다 반복

데이터가 깨지는 건 아니다. 캐시 히트면 ~1ns인데 무효화 후 재로드는 ~100ns이고, 이게 수천만 번 반복되면 체감 성능이 크게 떨어진다. 빠르라고 멀티스레드를 썼는데 오히려 싱글스레드보다 느려질 수 있다.

race condition은 같은 변수를 동시에 써서 결과가 틀려지는 문제이고, false sharing은 다른 변수인데 같은 캐시 라인에 있어서 느려지는 문제다.

더 보기

sunshinemoon · 2026