False Sharing 진단 — Cache Line Ping-Pong·Padding·측정
#한 줄 요약
“False Sharing = 다른 변수, 같은 cache line” 입니다. 코어 간 ping-pong이 일어나 10~100배까지 느려집니다.
#메커니즘
struct { int counter_a; // CPU 0 사용 int counter_b; // CPU 1 사용} stats; // 8 byte — 같은 64-byte line
CPU 0: writes counter_a → cache line state = Modified (CPU 0) → CPU 1 line state = Invalid
CPU 1: writes counter_b → coherence protocol triggered → CPU 0 line evict (flush to L2) → CPU 1 fetch from L2 (or CPU 0 cache) → CPU 1 cache state = Modified → CPU 0 = Invalid
* 매 access마다 *line bounces between caches* (ping-pong)실제로 공유되는 데이터는 하나도 없는데, 단지 같은 line에 들어 있다는 이유로 coherence가 계속 동작합니다.
그림으로 보면 두 코어가 같은 line을 두고 핑퐁하는 모습이 분명해집니다.
#MESI Protocol
M (Modified) — 이 cache만 valid, dirtyE (Exclusive) — 이 cache만 valid, cleanS (Shared) — 여러 cache valid, cleanI (Invalid) — 무효State 변화는 다음과 같이 일어납니다.
CPU 0 write: I → M, 다른 cache invalidate broadcastCPU 1 read after CPU 0 write: - CPU 0 cache의 M line → flush to memory - CPU 0: M → S, CPU 1: I → S
False sharing = 매번 M → I → M → I 반복.#측정 — 실 cycle
struct { atomic_int a; atomic_int b;} bad;
struct { alignas(64) atomic_int a; alignas(64) atomic_int b;} good;
void thread1_func(void *p) { for (int i = 0; i < 10M; i++) atomic_fetch_add(&bad.a, 1);}void thread2_func(void *p) { for (int i = 0; i < 10M; i++) atomic_fetch_add(&bad.b, 1);}Cortex-A72 4-core 환경에서 실측한 결과는 다음과 같습니다.
Bad (false sharing): 4.2 secGood (padded): 0.3 sec → 14x slowdown#Padding
struct counters { alignas(64) atomic_int a; char pad_a[64 - sizeof(atomic_int)];
alignas(64) atomic_int b; char pad_b[64 - sizeof(atomic_int)];};또는 C++17:
#include <new>struct alignas(std::hardware_destructive_interference_size) Counter { std::atomic<int> value;};
std::array<Counter, 4> counters; // 각 element가 다른 linehardware_destructive_interference_size는 일반적으로 64이고, Apple M1은 128입니다.
#흔한 false sharing 패턴
#1. Per-CPU 변수
int counters[NUM_CPUS]; // ← 8개 × 4 byte = 32 byte → 한 line 안each cpu: counters[cpu]++→ false sharingpadding을 넣거나 per-CPU memory로 분리해야 합니다.
struct counter_per_cpu { alignas(64) int value;};struct counter_per_cpu counters[NUM_CPUS];#2. Producer/Consumer Queue
struct queue { size_t head; // producer writes size_t tail; // consumer writes /* ... data ... */};producer와 consumer가 다른 코어에서 동작하면 head와 tail이 같은 line에 들어가서 ping-pong이 발생합니다.
struct queue { alignas(64) atomic_size_t head; char pad[64 - sizeof(atomic_size_t)]; alignas(64) atomic_size_t tail; char pad2[64 - sizeof(atomic_size_t)]; /* ... data ... */};#3. Spinlock 인접 데이터
struct { spinlock_t lock; int data1; // ← lock 잡힌 코어와 다른 코어가 동시 access 시 ping-pong int data2;} resource;lock과 data를 서로 다른 line에 분리해야 합니다.
#perf c2c — Cache-to-Cache 진단
sudo perf c2c record ./progsudo perf c2c report
# Output:# - HITM events (Hit in Modified state — false sharing 시그너처)# - Per-cache-line contention# - Source code locationLinux kernel 4.10 이상에서 사용할 수 있으며, false sharing을 탐지하는 가장 강력한 도구입니다.
#Intel VTune Memory Access
VTune의 Memory Access analysis는 다음과 같은 정보를 제공합니다.
- Per cache line latency
- Local vs Remote DRAM access
- Contended cache lines 보고
#Embedded — Cortex-A SMP
/* Linux on Cortex-A — 4 코어 */DEFINE_PER_CPU(int, my_counter); // 자동 padded
/* RTOS SMP — FreeRTOS 11 SMP 또는 Zephyr */static atomic_t counters[NUM_CORES] __attribute__((aligned(64)));Zephyr는 Z_KERNEL_STACK_DEFINE 등을 통해 자동으로 정렬을 맞춰 줍니다.
#False Sharing은 항상 나쁜가?
반드시 그렇지는 않습니다. Workload에 따라 다음과 같이 갈립니다.
- Read 위주 → 모든 cache S state, ping-pong 없음, OK
- Per-CPU 누적 → padding 필요
- 가끔 write → 측정해서 결정
모든 변수에 padding을 넣으면 line 하나당 실제 정보가 1 byte 수준으로 줄어 cache 효율이 떨어집니다.
#True Sharing — 진짜 공유 시
atomic_int global_counter;/* 모든 thread가 update */이런 경우는 진짜 공유이지 false sharing이 아닙니다. 해결책으로는 다음 두 가지가 있습니다.
- Per-CPU에서 누적한 뒤 주기적으로 합산
- Sharded counter
atomic_int counter[NUM_CPUS];
int total(void) { int sum = 0; for (i = 0) sum += counter[i]; return sum;}Read는 가끔이고 write가 자주 일어나는 경우라면 per-CPU shard를 쓰는 편이 좋습니다.
#Lock-free Queue 디자인
struct lockfree_spsc { alignas(64) atomic_size_t head; // producer-only alignas(64) atomic_size_t tail; // consumer-only alignas(64) T buf[CAPACITY]; // 별도 line};각 hot field가 서로 다른 line에 놓이도록 해서 false sharing을 0으로 만듭니다.
#자주 하는 실수
⚠️ 작은 변수만 padding
alignas(64) int a;int b; // ← a 같은 line에 들어감a 뒤 64 byte 영역은 다른 변수가 차지할 수 있습니다. 모든 변수에 alignas를 붙이거나 명시적으로 pad를 넣어야 합니다.
⚠️ Padding 안에 다른 data
struct foo { alignas(64) int a; char tmp[60]; alignas(64) int b; /* tmp 안 다른 데이터 두지 마라 — 그것도 line bouncing */};⚠️ Stack 변수 padding
void func(void) { alignas(64) int x; // ← stack alignment 보장 안 됨}GCC의 -mstackrealign 옵션을 쓰거나, 해당 변수를 heap이나 static 영역에 두는 방식으로 해결합니다.
⚠️ 작은 시스템에서 over-pad
/* Cortex-M7 — 32 byte cache line */alignas(64) int x; // ← 32 byte로 충분Embedded 환경에서는 cache line size를 확인한 다음 정확히 그 크기에 맞춰 align해야 합니다.
#정리
- False sharing은 다른 변수가 같은 line에 있어서 coherence ping-pong이 발생하는 현상입니다.
- 해결책은 line size에 맞춘
alignas(64)padding입니다. - 진단에는 perf c2c를 사용합니다.
- Producer/consumer queue와 per-CPU counter는 기본적으로 padding을 넣어야 합니다.
- True sharing은 별도의 문제이며, sharding으로 해결합니다.
- Cache line size는 Cortex-M7에서 32, 대부분의 CPU에서 64, Apple M1에서 128입니다.
다음 편은 Lock Contention을 다룹니다.
#관련 항목
Embedded Performance Engineering · 32 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 처리량 회복
관련 글
Cache Line 최적화 — Alignment·Prefetch·False Sharing 처리
64-byte line alignment, software prefetch, false sharing 회피, SoA·AoS 선택.
실전 사례 — CXL.mem 추가로 LLM inference KV cache 처리량 회복
70B 모델 KV cache가 HBM 한계를 넘어 throughput이 무너졌을 때, CXL.mem 256 GB pool 추가로 회복한 실전 케이스.
CXL 성능 프로파일링 도구 — cxl-cli·DAMON·perf-mem 활용
CXL.mem 환경 성능 도구 — cxl-cli 토폴로지·DAMON page activity·perf-mem로 보는 CXL 트래픽·numastat 통계.
이 글을 참조하는 글 (8)
- 실전 사례 — 8-core가 4-core를 넘으면 throughput이 떨어지는 이유— Embedded Performance Engineering
- SMP 성능 분석 — Per-Core·Affinity·Load Balance·Scalability— Embedded Performance Engineering
- Cache Coherency 프로토콜 — MESI·MOESI·Snoop·Directory— Embedded Performance Engineering
- Lock Contention 분석 — Wait·Hold·Convoy·측정 기법— Embedded Performance Engineering
- Concurrency 기초 — Concurrency vs Parallelism·Race·Memory Model— Embedded Performance Engineering
- False Sharing 해결 — Cache Line Padding·SoA 적용— Modern Embedded Recipes
- NUMA Memory Topology — numactl·numa_alloc·HBM 적용— Modern Embedded Recipes
- Cache Line Alignment — alignas·Padding·SoA 적용— Modern Embedded Recipes