본문으로 건너뛰기
Embedded Performance Engineering · 14/57

Cache Miss 3C Model 분석 — Compulsory·Capacity·Conflict

· Hawk · 5분 읽기

#한 줄 요약

**“3C — Compulsory·Capacity·Conflict”**입니다. 각각 원인과 대응이 다릅니다.

#Compulsory Miss (Cold Miss)

처음 access 시 무조건 miss입니다. Cache가 비어 있기 때문입니다.

int data[1000]; // 새로 할당 — cache 안에 없음
sum += data[0]; // ← compulsory miss

해결책은 사실상 불가피하지만, prefetch로 latency를 숨길 수 있습니다.

__builtin_prefetch(&data[i+16], 0, 0); // 16 element 앞 prefetch
for (i = 0; i < N; i++) {
sum += data[i];
}

ARM에서는 pld(preload data) 명령을 사용합니다.

#Capacity Miss

Working set이 cache 크기를 초과하면 데이터가 반복적으로 evict되고 다시 load됩니다.

// L1 D-cache 32 KB
int buf[16384]; // 64 KB → cache 못 다 넣음
for (iter = 0; iter < 100; iter++) {
for (i = 0; i < 16384; i++) {
sum += buf[i]; // 매 iter capacity miss
}
}

해결책은 blocking / tiling입니다.

#define BLOCK 4096 // L1 안에 들어가게
for (b = 0; b < 16384; b += BLOCK) {
for (iter = 0; iter < 100; iter++) {
for (i = b; i < b + BLOCK; i++) {
sum += buf[i];
}
}
}

각 BLOCK이 cache 안에 머무는 동안 iter를 100번 처리합니다.

#Conflict Miss

Set associativity 한계 때문에 같은 set으로 mapping되는 데이터들이 서로를 evict합니다.

// 4-way set assoc cache, line size 64
int A[2048]; // 8 KB
int B[2048]; // 8 KB
int C[2048];
// A, B, C가 같은 set에 mapping된다면
for (i = 0; i < 2048; i++) {
A[i] += B[i] * C[i]; // 매 access conflict
}

해결책:

  • Padding: array 크기를 non-power-of-2로 조정
  • Loop fission: 한 번에 적은 변수만 access
  • Cache line padding: 변수 사이에 padding 삽입
int A[2048];
char pad1[64]; // line offset 변경
int B[2048];
char pad2[64];
int C[2048];

#측정 — PMU 이벤트

Cortex-A53 perf events:

  • 0x03 L1D_CACHE_REFILL L1 D 미스 (refill 횟수)
  • 0x04 L1D_CACHE L1 D 액세스
  • 0x01 L1I_CACHE_REFILL L1 I 미스
  • 0x14 L1I_CACHE L1 I 액세스
  • 0x17 L2D_CACHE_REFILL L2 D 미스
  • 0x16 L2D_CACHE L2 D 액세스

L1 miss rate=L1D_CACHE_REFILLL1D_CACHE,L2 miss rate=L2D_CACHE_REFILLL2D_CACHE\text{L1 miss rate} = \frac{\text{L1D\_CACHE\_REFILL}}{\text{L1D\_CACHE}}, \quad \text{L2 miss rate} = \frac{\text{L2D\_CACHE\_REFILL}}{\text{L2D\_CACHE}}

Terminal window
perf stat -e r03,r04,r17,r16 ./prog
# 좋음: L1 miss < 5%, L2 miss < 30%
# 나쁨: L1 miss > 15%

#Cold vs Capacity vs Conflict 구분

증상원인
첫 실행만 느리고 두 번째부터 빠르면Compulsory(해소됨)
매번 일정하게 느리지만 작은 데이터엔 빠르면Capacity(working set 축소로 해결)
특정 stride에서만 느리면Conflict(padding으로 해결)

#Stride Pattern과 Conflict

// L1 D = 32 KB, 4-way set assoc, 64 byte line
// → 128 set, set당 4 line
// → 같은 set 주기 = 32 KB / 4 = 8 KB
int A[2048]; // 8 KB — set 모두 다 차지
int B[2048]; // 8 KB — A와 같은 set들에 매핑 → conflict
for (i = 0; i < 2048; i++) {
A[i] = B[i]; // ← 매번 conflict
}

C 표준 malloc도 page align이라 8KB나 64KB 같은 깔끔한 숫자에 정렬되기 때문에 conflict가 빈번합니다.

#False Sharing (SMP 시)

struct {
int counter_a; // CPU 0 사용
int counter_b; // CPU 1 사용
} stats;

두 변수가 같은 cache line에 있으면 한 CPU가 쓸 때 다른 CPU의 cache가 invalidate됩니다. Conflict miss와 비슷한 양상이지만 cache coherence 차원의 문제입니다.

해결책은 line padding입니다.

struct {
int counter_a;
char pad1[60]; // line 분리
int counter_b;
char pad2[60];
} stats;

C++17에서는 alignas(std::hardware_destructive_interference_size)를 활용합니다.

#Loop Tiling 실전

/* 회피 — N=1024, working set 8MB */
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
for (k = 0; k < N; k++)
C[i][j] += A[i][k] * B[k][j];
/* Good — block 64×64, working set 48 KB */
#define B 64
for (ii = 0; ii < N; ii += B)
for (jj = 0; jj < N; jj += B)
for (kk = 0; kk < N; kk += B)
for (i = ii; i < ii+B; i++)
for (j = jj; j < jj+B; j++)
for (k = kk; k < kk+B; k++)
C[i][j] += A[i][k] * B[k][j];

GEMM(matrix multiply)의 표준 최적화 기법입니다. BLIS·MKL·OpenBLAS 모두 multi-level tiling을 사용합니다.

#Prefetch — Software vs Hardware

#Hardware Prefetcher

Cortex-A57 이상은 stride를 자동으로 감지합니다. 같은 간격으로 access하는 패턴을 발견하면 자동으로 prefetch합니다.

#Software Prefetch

for (i = 0; i < N; i++) {
__builtin_prefetch(&A[i + 16]); // 16 element 앞
sum += A[i];
}

거리는 cache miss latency / 명령 latency 정도로 잡습니다. L1 miss가 100 cycle이고 명령이 1 cycle이라면 100 element 앞을 prefetch합니다.

너무 가까우면 효과가 없고, 너무 멀면 prefetch한 데이터가 cache에서 폐기됩니다.

#Inclusive vs Exclusive Cache 영향

Intel (inclusive):

  • L2 miss → L1·L2 둘 다 update → L1 conflict 가능

ARM (exclusive):

  • L2 miss → L1만 update, L2엔 안 들어감
  • L1 evict → L2 victim cache로 입력

ARM은 작은 L2도 효율적이라 데이터를 잘 retention합니다.

#자주 하는 실수

⚠️ “Random access 가 cache friendly” 오해

for (i = 0; i < N; i++) {
sum += data[random_index[i]]; // ← random — prefetcher 동작 안 함
}

Hardware prefetch는 stride detect만 합니다. Random access는 매번 cold miss를 일으킵니다.

⚠️ 깊은 union, struct로 false sharing

struct {
atomic_int next_id; // hot
char name[60];
atomic_int counter; // hot
} thing;

두 atomic이 같은 line에 있으면 SMP에서 false sharing이 발생합니다. 반드시 분리해야 합니다.

⚠️ Powers of 2 array stride

int matrix[1024][1024]; // 4 MB
sum += matrix[i][j]; // stride 4096 byte = page size — TLB miss·conflict

int matrix[1024][1025][1024][1024 + padding] 형태로 padding을 추가해야 합니다.

⚠️ Cold miss 무시

init_huge_array(arr); // 처음 한 번 — 모든 line cold miss
process(arr); // 작은 working set — fast

큰 array 초기화의 cold miss 비용은 무시할 수 없습니다. 지연 초기화를 고려해야 합니다.

#정리

  • 3C는 Compulsory·Capacity·Conflict입니다.
  • Compulsory는 prefetch로 latency를 숨깁니다.
  • Capacity는 blocking/tiling으로 working set을 축소합니다.
  • Conflict는 padding이나 rearrangement로 해결합니다.
  • 측정은 PMU의 L1D_CACHE_REFILL로 합니다.
  • False sharing은 SMP에서 발생하는 또 다른 conflict입니다.

다음 편은 Cache Line 최적화입니다.

#관련 항목

Embedded Performance Engineering · 15 of 57

  1. 1Embedded Performance Engineering — 임베디드 성능 엔지니어링 시리즈 소개
  2. 2임베디드 성능 분석 방법론 — Measure → Analyze → Optimize 사이클
  3. 3성능 지표 정의 — Latency·Throughput·Utilization 분석
  4. 4성능 측정의 기본 — Wall-Clock·CPU Cycle·Instruction Count
  5. 5성능 데이터 통계적 분석 — Percentile·Histogram·평균의 함정
  6. 6실시간 성능 분석 — WCET·Jitter·Deadline Miss 측정
  7. 7임베디드 벤치마킹 기초 — 재현성·Warmup·노이즈 제거
  8. 8성능 모델링 — Amdahl·Gustafson·Roofline Model 적용
  9. 9프로파일링 기법 개요 — Sampling vs Instrumentation·PGO·LTO
  10. 10CPU 파이프라인 분석 — 5-stage·Cortex-M·Cortex-A 비교
  11. 11Pipeline Stall 분석 — Data·Structural·Control Hazard·Forwarding
  12. 12Branch Prediction 분석 — Static·2-bit·BTB·BHT·Mispredict 비용
  13. 13Speculative Execution 분석 — OoO·Reorder Buffer·Register Renaming
  14. 14CPU Cache 기초 — L1·L2·L3·Set Associative·Replacement Policy
  15. 15Cache Miss 3C Model 분석 — Compulsory·Capacity·Conflict
  16. 16Cache Line 최적화 — Alignment·Prefetch·False Sharing 처리
  17. 17메모리 대역폭 분석 — STREAM·Roofline·Bus Saturation 측정
  18. 18SIMD·NEON 활용 — 128-bit Vector·Auto-Vectorization·SVE/SVE2
  19. 19PMU·HPM 하드웨어 카운터 분석 — 정밀 성능 진단
  20. 20임베디드 Bus Architecture — AHB·AXI·CHI 진화와 5-Channel
  21. 21Bus Contention 진단 — Arbitration·QoS·Starvation 측정
  22. 22DMA 성능 최적화 — Burst·Scatter-Gather·Chain·Cache 일관성
  23. 23DMA vs CPU Copy 성능 비교 — Break-even·Setup Overhead 실측
  24. 24Interrupt Latency 분석 — 진입·종료·Tail-Chaining·Late Arrival
  25. 25Interrupt Storm 처리 — NAPI·Rate-Limit·Polling 전환
  26. 26MMIO 접근 성능 — Cache Policy·Write-Combining·Volatile·Barrier
  27. 27Peripheral Clock 분석 — PLL·Divider·Gating·DVFS
  28. 28Power vs Performance 트레이드오프 — DVFS·Race-to-Idle·Big.LITTLE
  29. 29Thermal Throttling 분석 — Junction Temp·Trip Point·냉각
  30. 30CXL Interconnect 분석 — AI 시대 메모리 대역폭 확장
  31. 31Concurrency 기초 — Concurrency vs Parallelism·Race·Memory Model
  32. 32False Sharing 진단 — Cache Line Ping-Pong·Padding·측정
  33. 33Lock Contention 분석 — Wait·Hold·Convoy·측정 기법
  34. 34Spinlock 성능 분석 — Spin-Wait vs Context Switch·Ticket·MCS
  35. 35Mutex 성능 분석 — Futex·Adaptive·Priority Inheritance
  36. 36Reader-Writer Lock 성능 — Reader/Writer Priority·RCU·Seqlock
  37. 37Lock-Free 자료구조 성능 — CAS·ABA·Hazard Pointer·Epoch Reclamation
  38. 38Memory Ordering 분석 — Acquire·Release·Seq-Cst·ARM Relaxed Model
  39. 39Cache Coherency 프로토콜 — MESI·MOESI·Snoop·Directory
  40. 40SMP 성능 분석 — Per-Core·Affinity·Load Balance·Scalability
  41. 41Linux perf 기초 — stat·record·report 활용
  42. 42Linux perf 고급 — Raw Event·Tracepoint·perf script
  43. 43ftrace 활용 — function·function_graph·latency tracer
  44. 44eBPF·bpftrace 동적 트레이싱 — 커널 무수정 관측
  45. 45Flamegraph 분석 — On-CPU·Off-CPU·Differential
  46. 46ARM DS·Lauterbach 분석 — Hardware Trace 전문 도구
  47. 47Bare-metal 프로파일링 — GPIO·DWT·SysTick·ITM 활용
  48. 48NVIDIA Nsight Systems — GPU·NPU 포함 시스템 분석
  49. 49모던 프로파일러 비교 — Tracy·Hotspot·uftrace·Coz
  50. 50연속 프로파일링 — Parca·Pixie·Pyroscope·Tetragon
  51. 51실전 사례 — ISR Latency 100µs Deadline Miss 추적
  52. 52실전 사례 — Matrix Multiply가 예상의 10배 느린 이유
  53. 53실전 사례 — 8-core가 4-core를 넘으면 throughput이 떨어지는 이유
  54. 54실전 사례 — 카메라 1080p 60fps가 30fps로 떨어지는 이유
  55. 55CXL.mem 지연·대역폭 실측 — Direct·Switch·Pooled 토폴로지 비교
  56. 56CXL 성능 프로파일링 도구 — cxl-cli·DAMON·perf-mem 활용
  57. 57실전 사례 — CXL.mem 추가로 LLM inference KV cache 처리량 회복