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

Cache Line 최적화 — Alignment·Prefetch·False Sharing 처리

· Hawk · 4분 읽기

#한 줄 요약

**“Cache line 활용 = spatial locality 활용”**입니다. 한 번 fetch한 line 안에서 최대한 많은 일을 처리합니다.

#Cache Line 크기

CPULine Size
Cortex-M732 byte
Cortex-A53/A72/A7864 byte
Intel/AMD x8664 byte
Apple M1/M2128 byte (특이)
IBM POWER128 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;
};
// 또는 C11
alignas(64) uint32_t buffer[256];
// 또는 C++11
alignas(64) struct Foo { ... };

배열 정렬:

__attribute__((aligned(64))) static int matrix[1024][1024];

DMA와 SIMD에서는 필수입니다. Misaligned access는 2 cycle을 추가로 소모하거나 Cortex-M0에서는 fault를 일으킵니다.

64-byte line 안의 데이터 배치가 성능을 결정합니다. 같은 line에 무엇이 들어 있느냐에 따라 좋은/나쁜 패턴이 갈립니다.

64-byte cache line의 세 가지 배치 — 섞임/분리/false sharing

#False Sharing 방지

// 회피 — 두 atomic이 같은 line
struct 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 Structures
struct 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 Arrays
struct {
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]);
}

매개변수는 다음과 같습니다.

인자의미
addrprefetch 주소
rw0 = read, 1 = write (write intent)
locality0 = NTA (non-temporal), 1-3 = temporal hint (3=highest)

ARM 명령은 다음과 같습니다.

pld [r0, #64] ; preload data
pldw [r0, #64] ; preload data for write
pli [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 → memory
HAL_UART_Transmit_DMA(&huart, dma_buf, 256);
함수동작
Cleancache → memory (write-back)
Invalidatecache line 폐기
CleanInvalidateflush + 폐기

#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

  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 처리량 회복