에듀테크 프로젝트 대시보드
캐시 일관성 (MESI 프로토콜)
CPU 코어마다 자기만의 캐시가 있다. 코어 1이 캐시에 들고 있는 x 값을 코어 2가 바꾸면? 두 캐시가 서로 다른 값을 믿게 되는 불일치(incoherence)가 생긴다. 하드웨어는 MESI라는 상태 기계로 이 문제를 막는다.
메인 메모리는 느리다(수백 사이클). 그래서 각 코어는 자주 쓰는 데이터를 자기 L1/L2 캐시에 복사해 둔다. 코어가 여러 개면 같은 메모리 한 칸이 여러 캐시에 동시에 복사될 수 있다.
// x = 0 에서 시작. 코어 A, 코어 B 모두 캐시에 x를 들고 있음 코어 A: x = 42 → A의 캐시에만 42 기록 (write-back) 코어 B: print(x) → B의 캐시에는 아직 0 ... 옛날 값을 읽음!
이런 일이 생기면 락(뮤텍스)을 아무리 잘 걸어도 소용없다. 그래서 캐시들끼리 공용 버스로 서로를 감시(bus snooping)하며, 한 캐시가 값을 바꾸면 나머지 캐시의 복사본을 즉시 무효화한다.
| 상태 | 뜻 | 메모리와 일치? | 다른 캐시에도 있나? |
|---|---|---|---|
| M Modified | 내가 고쳤고, 나만 최신본을 가짐 | ❌ 메모리가 낡음 (내가 나중에 써줘야 함) | 없음 (나만 유효) |
| E Exclusive | 나만 가지고 있지만 아직 안 고침 | ✅ 일치 | 없음 |
| S Shared | 여러 캐시가 같은 값을 읽기용으로 공유 | ✅ 일치 | 있을 수 있음 |
| I Invalid | 이 줄은 쓰레기 — 다시 읽어와야 함 | — | — |
코어 3개가 하나의 변수 x(메모리 초기값 0)를 공유한다. 각 코어의 읽기 / 쓰기 버튼을 눌러 보며 캐시 상태(M·E·S·I)와 버스 메시지가 어떻게 흐르는지 관찰하자. 쓰기는 그 코어가 x를 1 증가시킨다.
x = 0| 지금 상태 | 내 코어가 읽음 | 내 코어가 씀 | 버스에서 "남이 읽음" | 버스에서 "남이 씀" |
|---|---|---|---|---|
| M | M (히트, 조용) | M (히트, 조용) | 메모리에 flush → S | 메모리에 flush → I |
| E | E (히트, 조용) | E → M (조용!) | → S | → I |
| S | S (히트) | 무효화 방송 → M | S 유지 | → I |
| I | 버스에 읽기 요청 → 다른 캐시 있으면 S, 없으면 E | 버스에 쓰기 요청(무효화) → M | — | — |
캐시는 바이트 단위가 아니라 캐시 라인(보통 64바이트) 단위로 움직인다. 서로 다른 스레드가 다른 변수를 고쳐도, 그 두 변수가 같은 64바이트 라인에 들어 있으면 매번 서로의 라인을 무효화시켜 성능이 급락한다.
// 나쁜 예: counter[0]은 스레드0, counter[1]은 스레드1이 사용 long counter[2]; // 8+8=16바이트 → 같은 캐시 라인! // 해결: 라인 크기만큼 패딩해서 서로 다른 라인에 배치 struct { long v; char pad[56]; } counter[2];
atomic)이나 락도 결국 이 무효화 트래픽을 유발한다. 여러 코어가 같은 변수를 자주 쓰면 아무리 잘 짜도 느리다 — 코어별로 따로 세고 나중에 합치는 설계가 빠르다.