이번 문서의 목표: 이 파일을 다 읽으면 write-through와 write-back이 실제로 몇 번의 메모리 쓰기를 발생시키는지 숫자로 비교하고, write-allocate와 no-write-allocate가 쓰기 미스 상황을 어떻게 다르게 처리하는지 설명하며, 포함(inclusive) 정책과 배제(exclusive) 정책의 차이를 설명할 수 있다.
이 편에서 새로 다루는 것
캐시의 직접사상·완전연관사상·세트연관사상, 그리고 태그·인덱스·오프셋 비트 계산은 독학사 2단계 컴퓨터구조 16편에서 이미 같은 조건 하나로 세 방식을 전부 비교했다. 이 편은 그 계산을 반복하지 않는다. 대신 16편이 다루지 않은 “쓰기(write)가 일어났을 때 캐시와 주기억장치는 어떻게 일치를 유지하는가”(4단계에서 새로 요구되는 주제)를 다룬다.
쉽게 말하면: 16편이 “읽기(read) 요청이 왔을 때 어디서 찾는가”를 다뤘다면, 이 편은 “쓰기(write) 요청이 왔을 때 캐시에만 써 두고 끝낼 것인가, 주기억장치까지 바로 반영할 것인가”를 다룬다.
1. 왜 쓰기는 읽기보다 더 까다로운가
캐시에서 데이터를 읽을 때는 캐시의 값을 그대로 CPU에 돌려주면 끝이다. 원본(주기억장치)의 값을 바꿀 필요가 없기 때문이다. 그런데 쓰기(write)는 다르다. CPU가 캐시에 있는 값을 새 값으로 바꾸고 나면, 캐시의 값과 주기억장치의 값이 서로 달라진다. 이 상태를 그대로 두면, 나중에 그 블록이 캐시에서 쫓겨났을 때(교체됐을 때) 주기억장치에는 옛날 값이 그대로 남아 있어 데이터 불일치가 생긴다. 이 문제를 해결하는 두 가지 대표 전략이 write-through와 write-back이다.
2. write-through: 쓸 때마다 바로 주기억장치까지
write-through(즉시 쓰기)는 CPU가 캐시에 값을 쓸 때마다 주기억장치에도 동시에 같은 값을 쓰는 방식이다.
- 장점: 캐시와 주기억장치의 값이 항상 같으므로 데이터 일관성 문제가 근본적으로 생기지 않는다. 캐시 블록이 교체될 때도 그냥 지워버리면 그만이다(주기억장치가 이미 최신 값을 갖고 있으므로).
- 단점: 쓰기가 일어날 때마다 느린 주기억장치까지 접근해야 하므로, 쓰기 연산이 많은 프로그램에서는 성능 손해가 크다.
3. write-back: 캐시에만 써 두고 나중에 한꺼번에
write-back(지연 쓰기)은 CPU가 캐시에만 값을 쓰고, 주기억장치에는 당장 반영하지 않는다. 대신 그 블록에 더티 비트(dirty bit)라는 표시를 켜 둔다. 더티 비트는 “이 블록은 캐시에서만 수정되어 주기억장치와 값이 다르다”는 신호다. 이 블록이 나중에 캐시에서 쫓겨날 때(교체될 때), 더티 비트가 켜져 있는 경우에만 그제서야 주기억장치에 값을 반영한다.
- 장점: 같은 블록에 여러 번 연속으로 쓰기가 일어나도 주기억장치 접근은 교체될 때 딱 한 번뿐이다. 쓰기가 많은 프로그램에서 유리하다.
- 단점: 더티 비트를 관리하는 하드웨어가 추가로 필요하고, 블록이 교체되기 전에 정전 등으로 시스템이 멈추면 그 수정 내용이 유실될 위험이 있다.
예제: 같은 블록에 5번 연속 쓰기가 일어날 때
한 블록에 CPU가 연속으로 5번 쓰기 연산을 수행하고, 그 뒤 이 블록이 캐시에서 교체된다고 하자.
| 정책 | 주기억장치에 실제로 쓰기가 발생하는 횟수 |
|---|---|
| write-through | 5회(쓸 때마다 매번) |
| write-back | 1회(교체될 때 더티 비트를 확인하고 딱 한 번) |
결과 해석: 같은 블록에 반복해서 쓰는 프로그램(예: 반복문 안에서 누적 합을 갱신하는 변수)일수록 write-back의 이득이 커진다. 반대로 한 번 쓰고 다시는 접근하지 않는 데이터가 많은 프로그램이라면 두 정책의 차이가 크지 않다.
자주 틀리는 점: write-back이 “느리다”고 오해하는 경우가 있다. 실제로는 정반대로, 쓰기 횟수가 많을 때는 write-back이 더 빠르다. write-through가 항상 안전하지만 매번 느리고, write-back은 하드웨어가 조금 더 필요하지만 반복 쓰기에서 훨씬 빠르다는 트레이드오프로 기억해야 한다.
4. 쓰기 미스(write miss) — 쓰려는 블록이 캐시에 없을 때
읽기 미스는 항상 블록을 캐시로 가져와야 하지만, 쓰기 미스는 두 가지 선택지가 있다.
- write-allocate(쓰기 할당): 쓰려는 블록을 먼저 주기억장치에서 캐시로 가져온 뒤, 그 캐시 안에서 값을 수정한다. 이후 그 블록을 다시 읽을 가능성이 있다고 가정하는 전략이다. 보통 write-back과 짝을 이룬다 — 어차피 캐시 안에서 계속 수정할 것이므로 미리 가져와 두는 편이 유리하다.
- no-write-allocate(쓰기 비할당): 캐시로 가져오지 않고 주기억장치에만 직접 값을 쓴다. 그 블록을 곧 다시 쓸 일이 없다고 가정하는 전략이다. 보통 write-through와 짝을 이룬다 — 어차피 매번 주기억장치까지 쓰는 정책이므로 캐시에 미리 담아 둘 이유가 적다.
| 조합 | 특징 |
|---|---|
| write-back + write-allocate | 가장 흔한 조합. 반복 쓰기·재사용이 많은 데이터에 유리 |
| write-through + no-write-allocate | 단순하고 안전하지만 쓰기 성능 이득이 적음 |
5. 다단계 캐시의 용량 설계 — L1과 L2가 겹칠 때
09편에서 L1·L2 두 단계의 평균 접근시간(AMAT)을 계산했다. 이번에는 그 두 캐시가 저장하는 내용이 서로 어떤 관계인지를 정한다.
- 포함 정책(inclusive): L1에 있는 모든 블록은 반드시 L2에도 있다. L1이 작으므로 L2가 L1의 내용을 항상 포함하고 있는 셈이다. 장점은 다른 코어가 이 블록을 캐시하고 있는지 확인할 때 L2만 보면 되어 캐시 일관성 확인이 쉬워진다(19편에서 다룰 멀티프로세서 캐시 일관성의 기반이 된다). 단점은 L2 용량 중 일부가 L1과 중복된 내용으로 낭비된다.
- 배제 정책(exclusive): L1에 있는 블록은 L2에는 없다. 두 캐시의 내용이 겹치지 않으므로 L1+L2를 합친 전체 용량을 낭비 없이 다 활용할 수 있다. 단점은 블록 교체 시 L1과 L2 사이에서 블록을 주고받는 로직이 더 복잡해진다.
예제: L1 32KB, L2 256KB, 포함 정책일 때 실질적으로 활용 가능한 총 용량
포함 정책에서는 L1의 내용이 L2 안에 그대로 다시 저장되어 있으므로, 두 캐시가 담을 수 있는 서로 다른 블록의 총량은 L2 용량과 같다.
반면 배제 정책이라면 두 용량을 겹치지 않게 합칠 수 있다.
결과 해석: 배제 정책이 용량 활용 면에서는 32KB(약 12.5퍼센트)만큼 더 유리하다. 그런데도 실제 CPU에서 포함 정책이 널리 쓰이는 이유는, 용량 손해보다 멀티코어 환경에서의 캐시 일관성 확인 비용 절감이 더 크다고 보기 때문이다 — 이것이 4단계 통합형 문항이 즐겨 묻는 “왜 이론상 손해인 방식을 실무에서 선택하는가” 유형의 전형적인 답이다.
핵심 정리
- write-through는 쓸 때마다 주기억장치까지 반영해 일관성은 안전하지만 느리고, write-back은 더티 비트로 지연시켰다가 교체 시 한 번만 반영해 반복 쓰기에서 빠르다.
- 쓰기 미스에서는 write-allocate(캐시로 가져와 수정, 보통 write-back과 짝)와 no-write-allocate(주기억장치에 직접, 보통 write-through와 짝)로 나뉜다.
- 포함 정책은 L1의 내용을 L2도 갖고 있어 캐시 일관성 확인이 쉽지만 실질 용량은 L2 크기로 제한되고, 배제 정책은 두 용량을 겹치지 않게 합쳐 쓸 수 있지만 관리가 복잡하다.
- 매핑 방식별 비트 계산과 LRU/FIFO 교체 자체는 2단계 16편에서 이미 다뤘으므로 이 편은 그 위에 얹히는 쓰기 정책·다단계 설계만 새로 다룬다.