Cache Line 최적화 — Alignment·Prefetch·False Sharing 처리
#한 줄 요약
**“Cache line 활용 = spatial locality 활용”**입니다. 한 번 fetch한 line 안에서 최대한 많은 일을 처리합니다.
#Cache Line 크기
| CPU | Line Size |
|---|---|
| Cortex-M7 | 32 byte |
| Cortex-A53/A72/A78 | 64 byte |
| Intel/AMD x86 | 64 byte |
| Apple M1/M2 | 128 byte (특이) |
| IBM POWER | 128 byte |
매크로:
#define CACHE_LINE_SIZE 64
#if defined(__cpp_lib_hardware_interference_size)#include <new>constexpr size_t CACHE_LINE = std::hardware_destructive_interference_size;#endif#Alignment
__attribute__((aligned(64))) struct hot_data { uint32_t counter; uint32_t flag;};
// 또는 C11alignas(64) uint32_t buffer[256];
// 또는 C++11alignas(64) struct Foo { ... };배열 정렬:
__attribute__((aligned(64))) static int matrix[1024][1024];DMA와 SIMD에서는 필수입니다. Misaligned access는 2 cycle을 추가로 소모하거나 Cortex-M0에서는 fault를 일으킵니다.
64-byte line 안의 데이터 배치가 성능을 결정합니다. 같은 line에 무엇이 들어 있느냐에 따라 좋은/나쁜 패턴이 갈립니다.
#False Sharing 방지
// 회피 — 두 atomic이 같은 linestruct counters { atomic_int producer_count; atomic_int consumer_count;};
// Good — line 분리struct counters { alignas(64) atomic_int producer_count; alignas(64) atomic_int consumer_count;};SMP 환경에서는 false sharing이 10x slowdown을 일으키는 경우가 흔합니다. 측정 시에는 cache-references가 폭증합니다.
#Hot-Cold Splitting
// 회피 — 자주 / 드물게 쓰는 field 섞임struct guest { int id; int active; char address[256]; // 드물게 사용 char email[128]; // 드물게 int last_login; // hot};
// Good — hot/cold 분리struct guest_hot { int id; int active; int last_login;};
struct guest_cold { char address[256]; char email[128];};hot loop이 guest_hot만 순회하므로 cache 효율이 올라갑니다.
#SoA vs AoS
// AoS — Array of Structuresstruct particle { float x, y, z, vx, vy, vz, mass; } parts[N];for (i = 0; i < N; i++) { parts[i].x += parts[i].vx * dt; // x, vx만 사용, y/z/mass도 fetch됨}
// SoA — Structure of Arraysstruct { float x[N], y[N], z[N]; float vx[N], vy[N], vz[N]; float mass[N];} parts_soa;for (i = 0; i < N; i++) { parts_soa.x[i] += parts_soa.vx[i] * dt; // x, vx만 fetch}SIMD에 최적입니다. 연속된 x 4개를 NEON 벡터로 load할 수 있습니다. 게임 엔진과 물리 시뮬에서는 표준 패턴입니다.
#Software Prefetch
for (i = 0; i < N; i++) { __builtin_prefetch(&data[i + 8], 0 /* read */, 0 /* no temp */); process(data[i]);}매개변수는 다음과 같습니다.
| 인자 | 의미 |
|---|---|
| addr | prefetch 주소 |
| rw | 0 = read, 1 = write (write intent) |
| locality | 0 = NTA (non-temporal), 1-3 = temporal hint (3=highest) |
ARM 명령은 다음과 같습니다.
pld [r0, #64] ; preload datapldw [r0, #64] ; preload data for writepli [r0, #64] ; preload instruction거리는 latency / cycle per iter로 잡습니다. L1 miss가 12 cycle이고 iter가 3 cycle이면 4 element 앞을 prefetch합니다.
#Hardware Prefetcher 활성화
Cortex-A는 stride를 자동으로 감지합니다. Cortex-M7은 MEMCTL register로 제어합니다.
SCB->CCR |= SCB_CCR_BP_Msk; // Branch prediction enable// Cortex-M7 prefetch는 자동 (instruction은)#Streaming Access — Non-Temporal Stores
for (i = 0; i < N; i++) { arr[i] = compute(i); // 한 번 쓰고 안 읽음 — cache 입력 의미 없음}ARM STNP(Store Non-temporal Pair)는 cache를 우회하는 store입니다. x86에서는 MOVNT에 해당합니다.
__builtin_nontemporal_store(value, &arr[i]);큰 buffer 초기화나 copy에서 효과를 보며 cache pollution을 방지합니다.
#Cache Line Pad — Producer/Consumer Queue
struct spsc_queue { alignas(64) atomic_size_t head; // producer만 쓰는 line char pad1[64 - sizeof(atomic_size_t)];
alignas(64) atomic_size_t tail; // consumer만 쓰는 line char pad2[64 - sizeof(atomic_size_t)];
alignas(64) T data[CAPACITY];};Producer와 consumer가 서로 다른 CPU core에 있을 때 false sharing을 회피할 수 있습니다.
#Cache Maintenance — DMA·Coherence
/* Cortex-M7 + DMA receive */uint8_t dma_buf[256] __attribute__((aligned(32)));
HAL_UART_Receive_DMA(&huart, dma_buf, 256);/* DMA wrote to memory, CPU cache는 stale */
SCB_InvalidateDCache_by_Addr((uint32_t*)dma_buf, 256);/* 이제 CPU read시 fresh data */
process(dma_buf);/* DMA transmit — CPU writes to buf */fill_data(dma_buf);SCB_CleanDCache_by_Addr((uint32_t*)dma_buf, 256); // flush cache → memoryHAL_UART_Transmit_DMA(&huart, dma_buf, 256);| 함수 | 동작 |
|---|---|
Clean | cache → memory (write-back) |
Invalidate | cache line 폐기 |
CleanInvalidate | flush + 폐기 |
#Hot Path 정렬
__attribute__((hot)) void critical_function(void) { ... }__attribute__((cold)) void error_handler(void) { ... }GCC가 hot 함수를 연속 배치해 I-cache locality를 향상시킵니다.
-fprofile-use로 PGO(Profile-Guided Optimization)를 적용하면 실제 실행 분포에 따라 배치합니다.
#자주 하는 실수
⚠️ Misaligned struct
struct { char flag; // 1 byte int value; // 4 byte — offset 1? 또는 4 (padding)? char name[3];} thing; // → 12 byte? sizeof 확인ARMv6+에서는 misaligned access를 일부 지원하지만 느립니다. Cortex-M0/M1은 fault를 일으킵니다.
⚠️ Prefetch 거리 잘못
__builtin_prefetch(&arr[i + 1]); // 너무 가까움 — 효과 없음__builtin_prefetch(&arr[i + 1000]); // 너무 멈 — evict 가능벤치마크 측정으로 최적 거리를 찾습니다.
⚠️ Volatile + cache line padding
volatile struct { alignas(64) int a; alignas(64) int b; } v;volatile은 컴파일러의 캐싱을 차단합니다. False sharing은 hardware 차원의 문제로 두 개념은 별개입니다.
⚠️ SoA를 모든 경우에 사용
Random access나 한 객체의 여러 field를 동시에 사용하는 패턴에서는 AoS가 유리합니다. 적용 전에 반드시 접근 패턴을 분석해야 합니다.
#정리
- Cache line은 Cortex-A와 Intel에서 64 byte이며
alignas로 정렬합니다. - False sharing은 multi-CPU 변수를 line 단위로 분리해 회피합니다.
- SoA는 SIMD와 streaming에 유리합니다.
- Software prefetch는 적절한 거리에서만 효과가 있습니다.
- Cortex-M7에서 DMA를 쓸 때는 cache maintenance가 필수입니다.
다음 편은 Memory Bandwidth입니다.
#관련 항목
Embedded Performance Engineering · 16 of 57
- 1Embedded Performance Engineering — 임베디드 성능 엔지니어링 시리즈 소개
- 2임베디드 성능 분석 방법론 — Measure → Analyze → Optimize 사이클
- 3성능 지표 정의 — Latency·Throughput·Utilization 분석
- 4성능 측정의 기본 — Wall-Clock·CPU Cycle·Instruction Count
- 5성능 데이터 통계적 분석 — Percentile·Histogram·평균의 함정
- 6실시간 성능 분석 — WCET·Jitter·Deadline Miss 측정
- 7임베디드 벤치마킹 기초 — 재현성·Warmup·노이즈 제거
- 8성능 모델링 — Amdahl·Gustafson·Roofline Model 적용
- 9프로파일링 기법 개요 — Sampling vs Instrumentation·PGO·LTO
- 10CPU 파이프라인 분석 — 5-stage·Cortex-M·Cortex-A 비교
- 11Pipeline Stall 분석 — Data·Structural·Control Hazard·Forwarding
- 12Branch Prediction 분석 — Static·2-bit·BTB·BHT·Mispredict 비용
- 13Speculative Execution 분석 — OoO·Reorder Buffer·Register Renaming
- 14CPU Cache 기초 — L1·L2·L3·Set Associative·Replacement Policy
- 15Cache Miss 3C Model 분석 — Compulsory·Capacity·Conflict
- 16Cache Line 최적화 — Alignment·Prefetch·False Sharing 처리
- 17메모리 대역폭 분석 — STREAM·Roofline·Bus Saturation 측정
- 18SIMD·NEON 활용 — 128-bit Vector·Auto-Vectorization·SVE/SVE2
- 19PMU·HPM 하드웨어 카운터 분석 — 정밀 성능 진단
- 20임베디드 Bus Architecture — AHB·AXI·CHI 진화와 5-Channel
- 21Bus Contention 진단 — Arbitration·QoS·Starvation 측정
- 22DMA 성능 최적화 — Burst·Scatter-Gather·Chain·Cache 일관성
- 23DMA vs CPU Copy 성능 비교 — Break-even·Setup Overhead 실측
- 24Interrupt Latency 분석 — 진입·종료·Tail-Chaining·Late Arrival
- 25Interrupt Storm 처리 — NAPI·Rate-Limit·Polling 전환
- 26MMIO 접근 성능 — Cache Policy·Write-Combining·Volatile·Barrier
- 27Peripheral Clock 분석 — PLL·Divider·Gating·DVFS
- 28Power vs Performance 트레이드오프 — DVFS·Race-to-Idle·Big.LITTLE
- 29Thermal Throttling 분석 — Junction Temp·Trip Point·냉각
- 30CXL Interconnect 분석 — AI 시대 메모리 대역폭 확장
- 31Concurrency 기초 — Concurrency vs Parallelism·Race·Memory Model
- 32False Sharing 진단 — Cache Line Ping-Pong·Padding·측정
- 33Lock Contention 분석 — Wait·Hold·Convoy·측정 기법
- 34Spinlock 성능 분석 — Spin-Wait vs Context Switch·Ticket·MCS
- 35Mutex 성능 분석 — Futex·Adaptive·Priority Inheritance
- 36Reader-Writer Lock 성능 — Reader/Writer Priority·RCU·Seqlock
- 37Lock-Free 자료구조 성능 — CAS·ABA·Hazard Pointer·Epoch Reclamation
- 38Memory Ordering 분석 — Acquire·Release·Seq-Cst·ARM Relaxed Model
- 39Cache Coherency 프로토콜 — MESI·MOESI·Snoop·Directory
- 40SMP 성능 분석 — Per-Core·Affinity·Load Balance·Scalability
- 41Linux perf 기초 — stat·record·report 활용
- 42Linux perf 고급 — Raw Event·Tracepoint·perf script
- 43ftrace 활용 — function·function_graph·latency tracer
- 44eBPF·bpftrace 동적 트레이싱 — 커널 무수정 관측
- 45Flamegraph 분석 — On-CPU·Off-CPU·Differential
- 46ARM DS·Lauterbach 분석 — Hardware Trace 전문 도구
- 47Bare-metal 프로파일링 — GPIO·DWT·SysTick·ITM 활용
- 48NVIDIA Nsight Systems — GPU·NPU 포함 시스템 분석
- 49모던 프로파일러 비교 — Tracy·Hotspot·uftrace·Coz
- 50연속 프로파일링 — Parca·Pixie·Pyroscope·Tetragon
- 51실전 사례 — ISR Latency 100µs Deadline Miss 추적
- 52실전 사례 — Matrix Multiply가 예상의 10배 느린 이유
- 53실전 사례 — 8-core가 4-core를 넘으면 throughput이 떨어지는 이유
- 54실전 사례 — 카메라 1080p 60fps가 30fps로 떨어지는 이유
- 55CXL.mem 지연·대역폭 실측 — Direct·Switch·Pooled 토폴로지 비교
- 56CXL 성능 프로파일링 도구 — cxl-cli·DAMON·perf-mem 활용
- 57실전 사례 — CXL.mem 추가로 LLM inference KV cache 처리량 회복
관련 글
실전 사례 — Matrix Multiply가 예상의 10배 느린 이유
1024×1024 matrix multiply가 이론값의 10배 느렸다. SIMD부터 의심했지만 진짜 범인은 캐시 미스 90%였다.
False Sharing 진단 — Cache Line Ping-Pong·Padding·측정
False sharing 원인. Cache coherence ping-pong. Padding으로 line 분리. 측정 방법.
DMA 성능 최적화 — Burst·Scatter-Gather·Chain·Cache 일관성
Burst size 최적화. Scatter-gather, chain. Cache clean/invalidate, double buffer.
이 글을 참조하는 글 (6)
- 메모리 대역폭 분석 — STREAM·Roofline·Bus Saturation 측정— Embedded Performance Engineering
- Cache Miss 3C Model 분석 — Compulsory·Capacity·Conflict— Embedded Performance Engineering
- CPU Cache 기초 — L1·L2·L3·Set Associative·Replacement Policy— Embedded Performance Engineering
- False Sharing 해결 — Cache Line Padding·SoA 적용— Modern Embedded Recipes
- ARM NEON 심화 — Matrix Multiply·FFT·Image Filter 적용— Modern Embedded Recipes
- Cache Line Alignment — alignas·Padding·SoA 적용— Modern Embedded Recipes