csapp 6단원 마무리
캐시는 S=2s개의 캐시 집합의 배열로 구성
각 집합은 E개의 캐시라인,
각 라인은 B=2b바이트듸 데이터 블록, 유효비트, 태그 비트로 구성된다
[ Valid(유효비트) | Tag(태그 비트들) | Data Block(데이터 블록 B바이트) ]
0
이면 무효 -> 미스로 처리1
이면 사용 가능 -> 태그 비교로 최종 확인각 집합에 라인 하나만 있는 가장 간단한 캐시다
주소는 태그, 집합 인덱스1, 블록 오프셋2으로 분할되어 사용
[ Tag(20) | Set Index(8) | Block Offset(4) ]
미스가 발생하기 쉬우며
미스 발생시, 새 블록은 주소의 집합 인덱스에 따라 결정된 집합의 유일한 라인에 저장
32-bit 주소, 직접 매핑
총 캐시 용량 C = 4KB
, 블록 크기 B = 16B
→ b = log2(16)=4
세트 수 S = 4096 / 16 = 256
→ s = log2(256)=8
태그 비트 수 t = 32 - (8+4) = 20
[ Tag(20) | Set Index(8) | Block Offset(4) ]
예) 주소 0xCAFEBABE
를 쪼개면
Block Offset = 하위 4비트 = 0xE
(블록 내부 14번째 바이트)
Set Index = 그 다음 8비트 = 0xAB
(세트 171번)
Tag = 상위 20비트 = 0xCAFEB
즉 이 주소는 세트 0xAB로 가고, 그 세트의 유일한 라인에 Tag 0xCAFEB가 저장돼 있어야 히트
각 집합에 E개 이상의 라인이 있는 캐시
집합 선택은 직접 매핑 캐시와 동일
라인 매칭 시 확인해야 할 라인이 늘어 더 복잡해짐
미스 발생 시 교체 정책이 필요함
오직 하나의 집합(S=1)만 있는 캐시
집합이 하나기에 집합 인덱스 비트 같은 건 없고
주소는 태그와 블록 오프셋으로만 구성
크고 빠른 캐시를 만들기 불리해 작은 캐시에 사용됨
쓰기 히트(Write hits) 처리:
쓰기-통과(Write-through):
캐시와 다음 하위 레벨 모두에 즉시 데이터를 쓴다
쓰기-뒤로(Write-back):
캐시에만 쓰고, 라인이 교체될 때까지 기다렸다가 다음 하위 레벨에 쓴다
쓰기 미스(Write misses) 처리:
쓰기-할당(Write-allocate):
미스된 블록을 하위 레벨에서 가져와 캐시에 로드하고 캐시에 쓴다
비-쓰기-할당(No-write-allocate):
미스된 블록을 캐시에 로드하지 않고 하위 레벨에 직접 쓴다
이 2가지-2가지를 조합해 4가지 조합 사용가능하다
정책 조합 | 첫 번째 쓰기(미스) | 두 번째 쓰기 | 메모리 쓰기 횟수 | 교체 시 추가 쓰기 |
---|---|---|---|---|
WT + NWA | 메모리 직행 | 메모리 직행 | 2 | 없음 |
WB + WA | 캐시에 적재 후 캐시에만 | 캐시에만 | 0(진행 중) | 1(퇴출 시 write-back) |
WT + WA | 적재 후 캐시+메모리 | 캐시+메모리 | 2 | 없음 |
WB + NWA | 메모리 직행 | 메모리 직행 | 2 | 없음(미스마다 미리 메모리에 씀) |
캐시는 데이터 뿐만이 아닌 명령어도 저장한다
i/d로 분리해서 말이다
명령어만 저장하는 케시는 i-cache
데이터만 저장하는 캐시는 d-cache
둘을 함께 저장 하는 캐시는 통합(unifided) 캐시
Intel Core i7을 예시로 캐시 계층을 설명한다
cpu 칩에 4개 코어
각 코어에 L1 i-cache, L1 d-cache, L2 통합 캐시
SRMA캐시 전부 CPU칩 내부에 있음
캐시 | 접근 시간(사이클) | 크기 C | 연관도 E | 블록 B | 세트 S |
---|---|---|---|---|---|
L1 i-cache | 4 | 32 KB | 8 | 64 B | 64 |
L1 d-cache | 4 | 32 KB | 8 | 64 B | 64 |
L2 통합 | 10 | 256 KB | 8 | 64 B | 512 |
L3 통합 | 40–75 | 8 MB | 16 | 64 B | 8,192 |
명령어 페치i
와 데이터 접근d
를 동시에 수행
읽기 중심i
과 읽기/쓰기 혼합d
으로 각기 최적화
L1: 매우 작고 빠름(≈4 cyc). 코어 전용, 레이턴시 최소화
L2: 코어 전용 통합 캐시, 용량↑ 레이턴시↑ (≈10 cyc). L1 완충
L3: 칩 내 공유 통합 캐시(≈40–75 cyc). 코어 간 데이터 공유/재사용 비용 절감
전부 온칩 SRAM → DRAM 왕복보다 훨씬 빠름
캐시 성능 주요 평가 기준:
히트율(Hit rate): 메모리 참조 중 히트의 비율
미스율(Miss rate): 메모리 참조 중 미스의 비율
미스 페널티(Miss penalty): 미스 때문에 추가로 필요한 시간
캐시크기가 클수록 히트율🔺 히트시간🔺
블록 크기가 크고 공간지역성 활용시
라인수🔻 히트율🔺 전송량🔺 패널티🔺
연관도(한 세트 안에 들어갈 수 있는 캐시 라인 수)
높아지면 충돌 미스🔻 히트시간🔺 패널티🔺
프로그램 지역성 개선을 통해 캐시 미스율을 낮출 수 있다
지역 변수의 반복 참조:
컴파일러가 이들을 레지스터 파일에 캐시해 시간 지역성 🔺
스트라이드-1 참조 패턴5:
캐시가 데이터를 연속적 블록으로 저장해 공간 지역성 🔺
요점 :
코드가 메모리를 “가깝고(캐시), 연속적으로(Stride-1), 작게(작업 집합)” 쓰도록 만들면 성능이 급격히 좋아진다
의미: 크기(size)와 스트라이드(stride)에 따른 읽기 처리량(MB/s) 지형도
시간 지역성의 능선: 작업 집합이 L1/L2/L3/DRAM에 들어맞는 구간에서 처리량이 급변(예시 수치: L1 구간 수십 GB/s vs DRAM 하한 수백 MB/s)
공간 지역성의 경사: 스트라이드가 커질수록 처리량이 감소. Stride-1일 때 프리페처가 가장 잘 작동
해석: “작게 읽고, 자주 재사용하며, 연속으로 접근” 할수록 산의 높은 지대를 밟음
6가지 루프 순서 (i, j, k의 순열)는 기능은 같지만 캐시 거동6이 다르다
내부 루프에서 어떤 배열 쌍을 주로 읽느냐에 따라 AB / AC / BC 세 클래스로 묶인다
각 클래스는 반복당 로드/스토어/미스 특성이 달라 성능 차이가 크다 실측에선 최고/최저가 ~40× 차이 난다
원칙
내부 루프는 Stride-1이 되도록 인접한 메모리를 훑게 만들 것(행 우선인 C에서는 보통 열보다 행을 안쪽)
불필요한 쓰기(store)와 미스를 최소화
필요 시 블로킹/타일링7 으로 작업 집합을 L1/L2에 머무르게 한다
메모리 계층: 작고 빠른 캐시 ↔ 크고 느린 DRAM. 실질 접근 속도는 지역성에 좌우됨
좋은 지역성 = 낮은 미스율 = 빠른 실행
체크리스트
Stride-1 순회(배열은 인접 원소부터)
루프 교환(loop interchange)8 으로 안쪽 루프를 연속 접근으로
블로킹(tiling) 으로 작업 집합을 캐시에 맞추기
누적 변수로 메모리 쓰기 횟수 감소
데이터 구조 개선: SoA(배열의 구조)로 필요한 필드만 연속하게
불필요한 임시/큰 객체 생성 자제, 재사용 증가
측정-검증: 실제 머신에서 처리량/미스율을 계측해 선택 확인
블록(=캐시 라인) 안에서 몇 번째 바이트인지 ↩
어느 세트(줄)의 라인을 볼지 ↩
(Least Recently Used) 가장 오래 안쓰는 라인 버리기 -> 시간적 지역성용 ↩
(Least Frequently Used) 가장 적게 쓰인 라인 버리기 -> 자주 참조되는 핫데이터용 ↩
연속된 메모리 주소 한 칸씩(인접 원소) 차례로 접근하는 패턴 ↩
코드의 메모리 접근이 캐시에 얼마나 잘 어울리는지 (메모리에 대한 케시효율) ↩
큰 문제(데이터)를 작은 문제(블록, 타일) 로 쪼개 작업 집합9이 캐시에 머무는 동안 계산을 끝내는 기법
↩
중첩 루프의 순서를 바꿔 안쪽 루프가 Stride-1이 되게 만드는 변환 (공간 지역성 극대화) ↩
지금 당장 필요하고 곧 다시 쓸 데이터 총량 ↩