초창기 컴퓨터 시스템의 메모리 계층 구조는 CPU 레지스터, 메인 메모리, 디스크 저장 장치라는 단 3개의 레벨로만 구성되었습니다. 하지만 기술이 발전하면서 CPU의 처리 속도와 메인 메모리(DRAM)의 접근 속도 사이의 성능 격차(performance gap)가 점점 더 심각해졌습니다.
이러한 문제를 해결하기 위해, 시스템 설계자들은 CPU 레지스터 파일과 메인 메모리 사이에 작고 매우 빠른 SRAM 기반의 캐시 메모리를 추가해야만 했습니다.

이러한 캐시들의 구체적인 구성 방식(arrangement)에는 시스템마다 상당한 다양성이 존재하지만, 여러 계층의 캐시를 두어 속도 차이를 완충한다는 기본 원리는 모두 동일합니다.
이후 섹션에서의 논의를 단순화하기 위해, 앞으로는 CPU와 메인 메모리 사이에 단 하나의 L1 캐시만 존재하는 간단한 메모리 계층 구조를 가정하고 설명을 진행하겠습니다.

m비트의 메모리 주소를 사용하는 컴퓨터 시스템(즉, M=개의 고유한 주소를 가짐)을 가정해 봅시다. 이러한 시스템을 위한 캐시는 다음과 같이 구성됩니다.

일반적으로 캐시의 구조는 (S, E, B, m)이라는 튜플(tuple)로 요약하여 표현할 수 있습니다.
캐시 크기(용량, C)의 정의
캐시의 크기 C는 모든 데이터 블록의 총합으로 계산됩니다. 태그 비트나 유효 비트 같은 부가 정보는 크기 계산에 포함하지 않습니다.C=S×E×B
CPU가 load 명령에 따라 특정 메모리 주소 A의 데이터를 읽으려고 할 때, 캐시는 이 주소 A의 비트들을 마치 매우 간단한 해시 함수를 사용하는 해시 테이블처럼 해석하여 원하는 데이터가 캐시에 있는지 즉시 알아냅니다.
m비트의 메모리 주소 A는 캐시의 파라미터 S와 B에 의해 다음과 같이 세 개의 필드로 나뉩니다.
| t bits (Tag) | s bits (Set Index) | b bits (Block Offset) | | m-1 (s+b) | (s+b)-1 b | b-1 0 |
데이터 탐색은 다음 3단계로 이루어집니다.
A의 가운데에 있는 s개의 세트 인덱스 비트(set index bits)를 확인합니다.s=10이면 0번부터 1023번 세트 중 하나를 가리킴)E개의 라인들 중에서 우리가 찾는 데이터가 들어있는 라인이 있는지 확인해야 합니다.A의 상위 t개의 태그 비트(tag bits)를 해당 세트 내의 모든(E개) 라인의 태그 비트와 비교합니다.A의 태그 비트와 일치해야 한다.A의 하위 b개의 블록 오프셋 비트(block offset bits)를 사용합니다.B바이트 크기의 데이터 블록 내에서 우리가 정확하게 원하는 워드(word)나 바이트(byte)의 상대적인 위치를 알려줍니다.| 기호 | 설명 |
|---|---|
| 기본 파라미터 | |
| m | 메모리 주소의 비트 수 |
| 고유한 메모리 주소의 총 개수 | |
| 캐시 구조 파라미터 | |
| 캐시 내의 세트(Set) 총 개수 | |
| E | 한 세트당 캐시 라인(Line)의 개수 (연관성, Associativity) |
| 한 캐시 라인 내의 블록(Block) 크기 (바이트 단위) | |
| 파생 파라미터 | |
| s | 세트 인덱스를 위한 주소 비트 수 |
| b | 블록 오프셋을 위한 주소 비트 수 |
| t=m−(s+b) | 태그를 위한 주소 비트 수 |
| C=S×E×B | 태그와 유효 비트를 제외한 캐시의 총 크기 (용량) |
캐시는 세트당 캐시 라인의 개수(E)에 따라 여러 종류로 나뉩니다. 세트당 단 하나의 라인(E=1)만을 갖는 캐시를 직접 매핑 캐시(direct-mapped cache)라고 합니다.
CPU, 레지스터 파일, L1 캐시, 메인 메모리로 구성된 시스템을 가정해 봅시다. CPU가 메모리 워드 w를 읽는 명령어를 실행하면, 먼저 L1 캐시에 w를 요청합니다.
w의 복사본을 가지고 있다면, L1 캐시 히트(L1 cache hit)가 발생합니다. 이 경우 캐시는 신속하게 w를 추출하여 CPU에 반환합니다.w가 없다면 캐시 미스(cache miss)가 발생합니다. 이때 CPU는 L1 캐시가 w를 포함하는 블록 전체의 복사본을 메인 메모리에 요청하여 가져올 때까지 반드시 대기(wait/stall)해야 합니다. 메모리로부터 요청된 블록이 도착하면, L1 캐시는 그 블록을 자신의 캐시 라인 중 하나에 저장하고, 저장된 블록에서 w를 추출한 뒤 CPU로 반환합니다.캐시가 요청이 히트인지 미스인지 판단하고, 요청된 워드를 추출하는 전체 과정은 다음과 같은 3단계로 구성됩니다. 이 단계들은 앞서 6.4.1절에서 설명한 일반적인 캐시 구조의 동작 원리와 동일합니다.
이 단계에서 캐시는 먼저 메모리 주소 w의 중간 부분에서 s개의 세트 인덱스 비트(set index bits)를 추출합니다.

00001 (2진수)이라면 이는 정수 1로 해석됩니다. 따라서 이 주소에 해당하는 데이터는 반드시 캐시의 1번 세트(set 1)에 저장되어야 함을 의미합니다. 캐시는 즉시 1번 세트를 선택하여 다음 단계를 진행합니다.이전 단계에서 특정 세트 i를 선택했으므로, 다음 단계는 우리가 찾는 워드 w의 복사본이 세트 i 안에 포함된 캐시 라인에 저장되어 있는지를 판단하는 것입니다.
직접 매핑 캐시(Direct-Mapped Cache)에서는 이 과정이 매우 간단하고 빠릅니다. 왜냐하면 정의에 따라 세트당 단 하나의 캐시 라인만 존재하기 때문입니다 (E=1).
따라서, 워드 w의 복사본이 해당 라인에 들어있다고 확신하려면 다음 두 가지 조건이 모두 만족되어야 합니다.
w의 태그와 일치해야 합니다.본문 예시(그림 6.29)에서는 이 과정이 다음과 같이 진행됩니다.

위 두 조건이 모두 만족되었으므로, 우리는 우리가 원하는 워드의 복사본이 바로 이 라인에 저장되어 있음을 확신할 수 있습니다. 즉, 캐시 히트(cache hit)가 발생한 것입니다.
반대로, 만약 유효 비트가 0이거나 태그가 일치하지 않았다면, 둘 중 하나라도 만족하지 못했다면 캐시 미스(cache miss)가 발생했을 것입니다.
일단 캐시 히트(hit)가 발생하면, 우리가 찾는 워드 w가 해당 블록 어딘가에 존재한다는 것을 알게 됩니다. 이 마지막 단계는 그 블록 내에서 원하는 워드가 정확히 어디서부터 시작하는지를 결정하는 과정입니다.
100(2진수)인 것은 우리가 찾는 워드 w의 복사본이 해당 블록의 4번째 바이트(byte 4)에서부터 시작한다는 것을 의미합니다. (여기서는 워드의 크기가 4바이트라고 가정합니다.) 따라서 캐시는 블록의 4, 5, 6, 7번 바이트를 묶어 하나의 워드로 CPU에 전달하게 됩니다.캐시 미스(miss)가 발생하면, 캐시는 메모리 계층 구조의 다음 레벨(예: 메인 메모리)로부터 요청된 블록을 가져와야 합니다. 그리고 이 새로운 블록을 주소의 세트 인덱스 비트(set index bits)가 가리키는 세트의 캐시 라인에 저장해야 합니다.
일반적으로, 만약 해당 세트가 유효한 캐시 라인들로 이미 가득 차 있다면, 기존 라인 중 하나는 반드시 교체(evicted)되어야 합니다.
그러나 직접 매핑 캐시(direct-mapped cache)의 경우, 각 세트는 정확히 하나의 라인(E=1)만을 포함하므로 이 교체 정책은 매우 간단하고 자명합니다(trivial).
캐시가 세트를 선택하고 라인을 식별하는 메커니즘은 극도로 단순해야만 합니다. 왜냐하면 하드웨어는 이 모든 과정을 수 나노초(nanoseconds) 안에 수행해야 하기 때문입니다. 하지만 인간에게는 이러한 비트 조작 방식이 혼란스러울 수 있으므로, 구체적인 예시를 통해 전체 과정을 명확히 이해해 보겠습니다.
다음과 같은 사양을 가진 직접 매핑 캐시가 있다고 가정합시다.
(S, E, B, m) = (4, 1, 2, 4)각 파라미터의 의미는 다음과 같습니다.
또한, 워드(word)의 크기는 1바이트라고 가정하겠습니다.
캐시를 처음 배울 때는 전체 주소 공간을 나열하고 주소 비트를 분할해 보는 것이 매우 유익합니다. 4비트 주소 체계는 다음과 같이 Tag, Index, Offset으로 나뉩니다.
t = m - s - b = 4 - 2 - 1 = 1)[4비트 주소 구조: t s s o]
| Tag(1) | Index(2) | Offset(1) |
이 주소 공간에서 주목할 만한 점들은 다음과 같습니다.
t s s) 메모리의 각 블록을 고유하게 식별할 수 있습니다. 예를 들어, 블록 0은 주소 0(0000), 1(0001)로 구성되고, 블록 1은 주소 2(0010), 3(0011)으로 구성됩니다.00)과 블록 4(인덱스 00)는 모두 0번 세트(set 0)로 매핑됩니다.0이고 블록 4의 태그는 1입니다.CPU가 1바이트 워드를 순차적으로 읽는다고 가정하고 캐시의 동작을 시뮬레이션해 봅시다.
초기 캐시 상태 (비어 있음):
(V = Valid bit)
| Set | V | Tag | block[0] | block[1] |
|---|---|---|---|---|
| 0 | 0 | |||
| 1 | 0 | |||
| 2 | 0 | |||
| 3 | 0 |
1. 주소 0 (0000)의 워드 읽기
0000 → Tag=0, Index=00(Set 0), Offset=00이기 때문입니다.1로, 태그를 0으로 설정합니다. 블록의 0번째 바이트 m[0]을 CPU에 반환합니다.| Set | V | Tag | block[0] | block[1] |
|---|---|---|---|---|
| 0 | 1 | 0 | m[0] | m[1] |
| 1 | 0 | |||
| 2 | 0 | |||
| 3 | 0 |
2. 주소 1 (0001)의 워드 읽기
0001 → Tag=0, Index=00(Set 0), Offset=11이고, 태그(0)도 일치합니다.m[1]을 즉시 CPU에 반환합니다. 캐시의 상태는 변하지 않습니다.3. 주소 13 (1101)의 워드 읽기
1101 → Tag=1, Index=10(Set 2), Offset=10이기 때문입니다.110x에 해당)을 가져와 2번 세트에 저장하고 m[13]을 CPU에 반환합니다.| Set | V | Tag | block[0] | block[1] |
|---|---|---|---|---|
| 0 | 1 | 0 | m[0] | m[1] |
| 1 | 0 | |||
| 2 | 1 | 1 | m[12] | m[13] |
| 3 | 0 |
4. 주소 8 (1000)의 워드 읽기
1000 → Tag=1, Index=00(Set 0), Offset=01이지만, 라인에 저장된 태그(0)가 주소의 태그(1)와 일치하지 않기 때문입니다.m[8]을 CPU에 반환합니다.| Set | V | Tag | block[0] | block[1] |
|---|---|---|---|---|
| 0 | 1 | 1 | m[8] | m[9] |
| 1 | 0 | |||
| 2 | 1 | 1 | m[12] | m[13] |
| 3 | 0 |
5. 주소 0 (0000)의 워드 읽기
0000 → Tag=0, Index=00(Set 0), Offset=0| Set | V | Tag | block[0] | block[1] |
|---|---|---|---|---|
| 0 | 1 | 0 | m[0] | m[1] |
| 1 | 0 | |||
| 2 | 1 | 1 | m[12] | m[13] |
| 3 | 0 |
충돌 미스는 실제 프로그램에서 흔하게 발생하며, 때로는 이해하기 힘든 성능 문제를 일으키는 원인이 됩니다. 특히 직접 매핑 캐시에서는 프로그램이 배열의 크기를 2의 거듭제곱(power of 2)으로 사용할 때 충돌 미스가 자주 발생합니다.
dotprod 함수 예제두 벡터의 내적(dot product)을 계산하는 다음 함수를 예로 들어보겠습니다.
float dotprod(float x[8], float y[8])
{
float sum = 0.0;
int i;
for (i = 0; i < 8; i++)
sum += x[i] * y[i];
return sum;
}
이 함수는 배열 x와 y에 대해 순차적으로 접근하므로 좋은 공간적 지역성을 가집니다. 따라서 높은 캐시 히트율을 기대할 수 있지만, 항상 그렇지는 않습니다.
다음과 같은 시스템 환경을 가정해 봅시다.
float 자료형은 4바이트입니다.x는 메모리 주소 0부터 시작하는 32바이트 연속 공간에 저장됩니다.y는 x 바로 뒤인 주소 32부터 시작합니다.sum은 CPU 레지스터에 저장되어 메모리 참조가 필요 없다고 가정합니다.이 가정 하에, 각 배열 요소 x[i]와 y[i]는 항상 동일한 캐시 세트로 매핑됩니다.
| 요소 | 주소 | 세트 인덱스 | 요소 | 주소 | 세트 인덱스 | |
|---|---|---|---|---|---|---|
| x[0] | 0 | 0 | y[0] | 32 | 0 | |
| x[1] | 4 | 0 | y[1] | 36 | 0 | |
| x[2] | 8 | 0 | y[2] | 40 | 0 | |
| x[3] | 12 | 0 | y[3] | 44 | 0 | |
| x[4] | 16 | 1 | y[4] | 48 | 1 | |
| x[5] | 20 | 1 | y[5] | 52 | 1 | |
| x[6] | 24 | 1 | y[6] | 56 | 1 | |
| x[7] | 28 | 1 | y[7] | 60 | 1 |
(세트 인덱스 = (주소 / 블록 크기) % 세트 개수 = (주소 / 16) % 2)
x[0]에 접근합니다. 이는 미스(miss)이며, x[0]~x[3]을 포함하는 블록이 0번 세트에 로드됩니다.y[0]에 접근합니다. 이 또한 미스이며, y[0]~y[3]을 포함하는 블록이 0번 세트로 로드됩니다. 이 과정에서 바로 이전에 가져왔던 x의 블록을 덮어쓰게(교체하게) 됩니다.x[1]에 접근합니다. 이는 또 다시 미스입니다. 왜냐하면 y 블록이 x 블록을 덮어썼기 때문입니다. 결국 x[0]~x[3] 블록이 다시 0번 세트로 로드되며, 이번에는 y 블록을 덮어씁니다.이것이 바로 충돌 미스입니다. x와 y에 대한 모든 후속 참조는 x의 블록과 y의 블록 사이를 계속 왔다 갔다 하며 서로를 쫓아내는 결과를 낳습니다.
스래싱 (Thrashing)
캐시가 동일한 세트의 캐시 블록들을 반복적으로 로드하고 축출(evicting)하는 모든 상황을 설명하는 용어입니다.
결론적으로, 프로그램의 공간적 지역성이 좋고 캐시에 x와 y의 블록을 모두 담을 공간이 있음에도 불구하고, 두 블록이 동일한 캐시 세트로 매핑되기 때문에 모든 참조가 충돌 미스로 이어집니다. 이러한 종류의 스래싱으로 인해 프로그램 실행 속도가 2~3배까지 느려지는 것은 드문 일이 아닙니다.
다행히도, 프로그래머는 이 현상을 인지하기만 하면 스래싱 문제를 쉽게 해결할 수 있습니다. 한 가지 간단한 해결책은 각 배열의 끝에 B 바이트(블록 크기)만큼의 패딩(padding)을 추가하는 것입니다.
예를 들어, float x[8] 대신 float x[12]로 선언해 봅시다.
x 배열의 크기: 12 * 4 bytes = 48 bytesy 배열의 시작 주소: 48이렇게 하면 배열 요소와 세트 간의 매핑이 다음과 같이 변경됩니다.
| 요소 | 주소 | 세트 인덱스 | 요소 | 주소 | 세트 인덱스 | |
|---|---|---|---|---|---|---|
| x[0] | 0 | 0 | y[0] | 48 | 1 | |
| x[1] | 4 | 0 | y[1] | 52 | 1 | |
| x[2] | 8 | 0 | y[2] | 56 | 1 | |
| x[3] | 12 | 0 | y[3] | 60 | 1 | |
| x[4] | 16 | 1 | y[4] | 64 | 0 | |
| x[5] | 20 | 1 | y[5] | 68 | 0 | |
| x[6] | 24 | 1 | y[6] | 72 | 0 | |
| x[7] | 28 | 1 | y[7] | 76 | 0 |
x 배열 끝에 패딩을 추가함으로써, 이제 x[i]와 y[i]는 서로 다른 세트로 매핑됩니다. 예를 들어, x[0]은 0번 세트로, y[0]은 1번 세트로 매핑됩니다. 이로써 두 배열의 블록이 캐시 내에 평화롭게 공존할 수 있게 되어, 파괴적인 스래싱 현상이 사라지고 캐시 히트율이 극적으로 향상됩니다.
사용자는 왜 캐시가 주소의 상위 비트(high-order bits) 대신 중간 비트(middle bits)를 세트 인덱스로 사용하는지 궁금할 수 있습니다. 여기에는 중간 비트를 사용하는 것이 더 나은 합당한 이유가 있습니다.

중간 비트를 인덱스로 사용하는 이유는 프로그램의 공간적 지역성(spatial locality)을 최대한 활용하기 위함입니다. 중간 비트를 사용하면 메모리 상에서 서로 인접한(contiguous) 블록들이 캐시의 서로 다른 세트로 분산되어 매핑됩니다. 이는 캐시 공간을 효율적으로 사용하게 하여 히트율을 높입니다.
만약 주소의 상위 비트를 인덱스로 사용하면, 연속적인 메모리 블록들이 동일한 캐시 세트로 매핑되는 문제가 발생합니다.
0, 1, 2, 3... 에 해당하는 여러 연속된 블록들이 모두 동일한 인덱스 값을 갖게 됩니다.Block 0, Block 1, Block 2, Block 3 순서로 접근할 것입니다. 하지만 이 블록들은 모두 Set 0으로만 매핑됩니다. 결국 Block 1이 Block 0을 쫓아내고, Block 2가 Block 1을 쫓아내는 스래싱(thrashing)이 발생합니다. 캐시의 다른 세트들이 텅 비어있음에도 불구하고, 캐시는 특정 순간에 배열의 단 하나의 블록 크기만큼의 조각만을 담을 수 있게 됩니다. 이는 캐시 공간의 매우 비효율적인 사용입니다.이와 대조적으로, 중간 비트를 인덱스로 사용하면 인접한 블록들은 항상 서로 다른 캐시 세트로 매핑됩니다.
Tag와 Index 비트 경계에 있는 비트부터 순차적으로 변하게 됩니다. 즉, Index 비트가 가장 먼저 바뀝니다.Block 0은 Set 0으로, Block 1은 Set 1로, Block 2는 Set 2로... 이런 식으로 데이터가 캐시의 여러 세트에 골고루 분산되어 저장됩니다.결론적으로, 중간 비트 인덱싱은 순차적 메모리 접근 패턴이 캐시의 특정 부분에만 집중되지 않고 캐시 전체에 고르게 퍼지도록 보장하는, 영리한 하드웨어 설계 기법입니다.
직접 매핑 캐시에서 발생하는 충돌 미스(conflict miss) 문제는, 각 세트가 정확히 하나의 라인만 가질 수 있다(E=1)는 제약 조건에서 비롯됩니다.
1 < E < C/B 조건을 만족하는 캐시를 일반적으로 E-way 세트 연관 캐시(E-way set associative cache)라고 부릅니다. (여기서 C는 캐시 크기, B는 블록 크기입니다.)E = C/B가 되는 극단적인 경우(완전 연관 캐시)는 다음 섹션에서 다룹니다.
세트 연관 캐시에서 데이터 탐색의 첫 번째 단계인 '세트 선택' 과정은 직접 매핑 캐시와 완전히 동일(identical)합니다.

이 원리는 그림 6.33에 요약되어 있으며, 세트 연관성의 정도(E의 값)와는 무관하게 동일한 방식으로 동작합니다.
세트 연관 캐시에서의 라인 일치 확인 과정은 직접 매핑 캐시보다 더 복잡(involved)합니다. 그 이유는 요청된 워드가 세트 안에 있는지 판별하기 위해, 세트 내에 있는 여러 라인(E개)의 태그와 유효 비트를 모두 검사해야 하기 때문입니다.

이 과정을 이해하기 위해 연관 메모리(associative memory)라는 개념을 도입할 수 있습니다.
address → valuekey → value (키 검색 기반)세트 연관 캐시의 각 세트(set)는 바로 이 작은 연관 메모리처럼 동작한다고 생각할 수 있습니다.
라인 일치 확인 과정에서 캐시 하드웨어는 CPU가 보낸 주소의 태그를 '입력 키'로 사용합니다. 그리고 이 '입력 키'를 현재 선택된 세트 안에 있는 모든 라인들의 '키(저장된 태그 + 유효 비트)'와 동시에, 병렬적으로 비교합니다.
여기서 중요한 아이디어는, 해당 세트로 매핑되는 모든 메모리 블록은 그 세트 안의 어떤 라인에든(any line) 위치할 수 있다는 점입니다. 따라서 캐시는 반드시 세트 내의 각 라인을 모두 검색하여, 유효 비트가 1이고 태그가 주소의 태그와 일치하는 라인이 있는지 찾아야 합니다. 만약 그런 라인을 찾으면, 캐시 히트(cache hit)가 발생합니다.
일단 캐시 히트가 발생하여 원하는 데이터가 담긴 라인이 확정되면, 마지막 단계인 '워드 선택' 과정은 이전(직접 매핑 캐시)과 동일합니다.
주소의 하위 비트인 블록 오프셋(block offset)을 사용하여, 블록 내에서 CPU가 정확하게 원하는 워드의 위치를 찾아내어 반환합니다.
만약 CPU가 요청한 워드가 현재 선택된 세트 내의 어떤 라인에도 저장되어 있지 않다면, 캐시 미스(cache miss)가 발생합니다. 이 경우 캐시는 해당 워드를 포함하는 블록을 메모리에서 가져와야 합니다.
하지만 캐시가 블록을 가져온 후, 세트 내의 어떤 라인을 교체해야 할까요?
프로그래머가 캐시 교체 정책에 대한 지식을 자신의 코드에 직접적으로 활용하기는 매우 어렵기 때문에, 여기서는 아주 자세한 내용까지는 다루지 않겠습니다.
이러한 모든 정교한 정책들은 추가적인 시간과 하드웨어를 필요로 하는 단점이 있습니다.
하지만 메모리 계층 구조에서 CPU로부터 멀어질수록(하위 계층으로 갈수록), 미스 발생으로 인한 비용(성능 저하)은 더욱 비싸집니다. 따라서 하위 계층 캐시일수록, 좋은 교체 정책을 사용하여 미스를 최소화하는 것이 더욱 가치 있는 일이 됩니다.

완전 연관 캐시는 세트 연관 캐시의 가장 극단적인 형태로, 단 하나의 세트(set)가 캐시의 모든 라인을 포함하는 구조입니다. 즉, E = C/B 인 경우를 말합니다. (E: 세트당 라인 수, C: 캐시 전체 크기, B: 블록 크기)
이는 메모리 블록이 캐시 내의 어떤 비어있는 라인이든 자유롭게 들어갈 수 있다는 의미입니다.

완전 연관 캐시에서의 세트 선택 과정은 매우 간단하고 자명합니다(trivial). 왜냐하면 캐시 전체가 단 하나의 세트로 이루어져 있기 때문입니다.
완전 연관 캐시에서의 라인 일치 확인과 워드 선택 과정은 세트 연관 캐시와 동일한 방식으로 동작합니다. 주된 차이점은 규모(scale)의 문제입니다.

수많은 태그를 병렬적으로 검색해야 하므로, 크면서(large) 동시에 빠른(fast) 완전 연관 캐시를 만드는 것은 기술적으로 매우 어렵고 비용이 많이 듭니다.
결과적으로, 완전 연관 캐시는 다음과 같은 소규모 캐시에만 적합합니다.
지금까지 살펴본 것처럼, 읽기(read) 작업에 대한 캐시의 동작은 비교적 직관적입니다. 먼저 원하는 워드 w의 복사본을 캐시에서 찾고, 히트(hit)하면 즉시 반환합니다. 미스(miss)가 발생하면 w를 포함하는 블록을 하위 메모리 계층에서 가져와 캐시 라인에 저장한 후 w를 반환합니다.
하지만 쓰기(write) 작업의 상황은 조금 더 복잡합니다. 이 복잡성은 크게 두 가지 질문으로 나뉩니다.
CPU가 쓰려는 워드 w가 이미 캐시에 존재하는 경우(쓰기 히트), 캐시는 두 가지 정책 중 하나를 따릅니다.
w의 복사본을 갱신하는 즉시, 해당 캐시 블록을 하위 메모리 계층에 바로 씁니다.w의 복사본만 갱신하고, 하위 계층으로의 갱신은 가능한 한 미룹니다. 갱신된 블록은 교체 알고리즘에 의해 캐시에서 축출(evicted)될 때만 하위 메모리 계층에 써집니다.CPU가 쓰려는 워드 w가 캐시에 없는 경우(쓰기 미스), 두 가지 접근 방식이 있습니다.
일반적으로 이 정책들은 다음과 같이 조합되어 사용됩니다.
쓰기 작업을 위한 캐시 최적화는 매우 미묘하고 어려운 문제입니다. 여기서 다룬 내용은 빙산의 일각에 불과하며, 세부 사항은 시스템마다 다르고 종종 독점 기술이거나 문서화가 잘 되어있지 않습니다.
합리적으로 캐시 친화적인(cache-friendly) 프로그램을 작성하려는 프로그래머에게는, 정신적인 모델(mental model)로서 나중 쓰기, 쓰기 시 할당(Write-Back, Write-Allocate) 방식의 캐시를 가정하는 것을 제안합니다. 여기에는 몇 가지 이유가 있습니다.
즉시 쓰기보다 나중 쓰기 방식이 더 선호됩니다. 예를 들어, 메인 메모리를 디스크의 캐시로 사용하는 가상 메모리 시스템은 나중 쓰기만을 사용합니다. 또한, 로직 집적도가 높아지면서 나중 쓰기의 복잡성이라는 단점이 줄어들어, 현대 시스템의 모든 캐시 레벨에서 나중 쓰기 방식이 점점 더 많이 채택되고 있습니다.나중 쓰기, 쓰기 시 할당 방식은 읽기 작업과 마찬가지로 지역성을 활용하려는 대칭적인 접근 방식을 취합니다.지금까지 우리는 캐시가 프로그램 데이터만을 저장한다고 가정했지만, 사실 캐시는 데이터뿐만 아니라 명령어(instructions)도 저장할 수 있습니다.
현대의 프로세서는 별도의 I-cache와 D-cache를 포함합니다. 여기에는 여러 가지 중요한 이유가 있습니다.
본문 그림 6.38은 Intel Core i7 프로세서의 캐시 계층 구조를 보여줍니다.

이 계층 구조의 흥미로운 특징은 모든 SRAM 캐시 메모리가 CPU 칩 내에 포함되어 있다는 점입니다. 본문 그림 6.39는 이 Core i7 캐시들의 기본 특성(용량, 연관성, 접근 속도 등)을 요약하여 보여줍니다.
캐시의 성능은 여러 가지 지표로 평가됩니다.
캐시 메모리의 비용과 성능 간의 상충 관계(trade-off)를 최적화하는 것은 실제 벤치마크 코드를 통한 광범위한 시뮬레이션이 필요한 미묘한 작업이므로 이 책의 범위를 벗어납니다. 하지만, 몇 가지 정성적인 상충 관계는 식별할 수 있습니다.
큰 블록은 장단점이 섞여 있는 양날의 검(mixed blessing)입니다.
여기서의 쟁점은 세트당 캐시 라인의 수인 파라미터 E의 선택이 미치는 영향입니다.
E 값)은 충돌 미스로 인한 스래싱(thrashing) 현상에 대한 캐시의 취약성을 감소시킵니다.연관성의 선택은 궁극적으로 히트 시간과 미스 패널티 사이의 상충 관계로 귀결됩니다. 전통적으로, 클럭 속도를 높이는 고성능 시스템들은 미스 패널티가 불과 몇 사이클인 L1 캐시에는 낮은 연관성을 채택하고, 미스 패널티가 더 큰 하위 레벨 캐시에는 높은 연관성을 채택하는 경향이 있습니다. 예를 들어, Intel Core i7 시스템에서 L1과 L2 캐시는 8-way 연관이고, L3 캐시는 16-way 연관입니다.
일반적으로, 계층 구조에서 더 아래에 있는 캐시일수록 즉시 쓰기보다는 나중 쓰기 방식을 사용할 가능성이 더 높습니다.