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

Pipeline Stall 분석 — Data·Structural·Control Hazard·Forwarding

· Hawk · 4분 읽기

#한 줄 요약

Stall은 pipeline bubble입니다. 명령이 진행하지 못하면서 IPC가 손실됩니다.

#Data Hazard 3종 (RAW·WAR·WAW)

#RAW (Read After Write) — 진짜 의존성

add r0, r1, r2 ; r0 = r1 + r2
sub r3, r0, r4 ; r0 사용 — RAW

subr0를 읽을 때 add 결과가 필요합니다. Forwarding으로 해결합니다.

#WAR (Write After Read) — 반의존성

add r0, r1, r2 ; r1 read
sub r1, r3, r4 ; r1 write

In-order pipeline에서는 문제가 없습니다. OoO에서는 register renaming으로 해결합니다.

#WAW (Write After Write) — 출력 의존성

add r0, r1, r2
sub r0, r3, r4 ; r0 다시 write

In-order는 자동으로 처리되고, OoO는 renaming으로 처리합니다.

#Forwarding (Bypass)

Forwarding path — EX 단계 출력을 다음 명령의 EX 입력으로 직접 연결

EX 단계의 출력을 다음 명령의 EX 입력에 직접 연결합니다. 별도 wire로 register file을 우회합니다. ARM Cortex-A에는 Operand Forwarding Unit이 있습니다.

#Load-Use Stall — Forwarding 불가능 케이스

ldr r0, [r1] F D E M W
↑ load 결과 = M 단계 끝
add r2, r0, r3 F D E ; E 단계에 r0 필요 — but M 단계 안 끝남
─── 1 cycle bubble ───

ARM Cortex-M3/M4의 load-use penalty는 1 cycle입니다. Cortex-M7은 2 cycle입니다.

#해결책은 명령 재정렬

; 회피
ldr r0, [r1]
add r2, r0, r3 ; ← stall
; Good
ldr r0, [r1]
add r4, r5, r6 ; 독립 명령 삽입
add r2, r0, r3 ; ← load 결과 사용 시점에 준비됨

-O2 이상에서 컴파일러가 자동으로 재정렬합니다. volatile 변수는 순서가 고정되어 재정렬되지 않습니다.

#Structural Hazard

; 가상 — 단일 memory port 가정
ldr r0, [r1] ; F D E M ← memory
ldr r2, [r3] ; F D E ← M 단계에 또 memory 시도 → stall

Harvard architecture를 쓰면 instruction memory와 data memory가 분리되어 동시에 액세스할 수 있습니다.

ARM Cortex-M3/M4는 single port Harvard입니다 (I/D 통합 bus). M7은 dual port TCM을 가집니다.

#Control Hazard

beq r0, r1, label ; F D E
↑ E 단계에 분기 확정
nop ; F (이미 fetch — 분기 시 flush)
nop ; F

branch prediction으로 해결합니다. 별도 편에서 다룹니다.

#ARM Cortex-M4 Cycle 측정 예

volatile uint32_t a, b, c, d;
void test_no_stall(void) {
/* 독립 명령 — stall 없음 */
asm volatile (
"add r0, r1, r2 \n"
"add r3, r4, r5 \n"
"add r6, r7, r8 \n"
);
}
void test_raw_chain(void) {
/* RAW chain — forwarding으로 처리 */
asm volatile (
"add r0, r1, r2 \n"
"add r3, r0, r4 \n" /* r0 의존 */
"add r5, r3, r6 \n" /* r3 의존 */
);
}
void test_load_use(void) {
asm volatile (
"ldr r0, [%0] \n"
"add r1, r0, r0 \n" /* load-use stall 1 cycle */
:: "r"(&a)
);
}

DWT CYCCNT로 측정하면 test_no_stalltest_raw_chain같은 cycle이고, test_load_use1 cycle이 더 걸립니다.

#PMU STALL Counter (Cortex-A)

Cortex-A53 Performance Monitoring Unit의 이벤트는 다음과 같습니다.

Event의미
0x23 STALL_FRONTENDF·D 단계 stall입니다 (cache miss·branch mispredict)
0x24 STALL_BACKENDE·M 단계 stall입니다 (data dependency·memory)
0x73 STALL_BACKEND_MEMmemory bound stall입니다
/* perf_event_open으로 측정 */
struct perf_event_attr attr = {
.type = PERF_TYPE_RAW,
.config = 0x24, // STALL_BACKEND
};
int fd = perf_event_open(&attr, 0, -1, -1, 0);

STALL_FRONTEND > STALL_BACKEND이면 fetch bound입니다 (cache miss나 mispredict가 의심됩니다). STALL_BACKEND > STALL_FRONTEND이면 compute나 memory bound입니다 (data dependency나 DRAM 대기입니다).

#Out-of-Order Renaming

Cortex-A72 등 OoO 코어의 동작은 다음과 같습니다.

ISA 레벨: r0 = r1 + r2
r3 = r0 + r4
r0 = r5 + r6 (WAW!)
r7 = r0 + r8
Renaming 후:
v10 = v1 + v2
v11 = v10 + v4
v12 = v5 + v6 (WAW 해소)
v13 = v12 + v8

Architectural register r0의 두 정의가 physical register 두 개로 분리됩니다. 의존성 cycle이 없는 분리된 stream으로 실행할 수 있습니다.

#Conditional Execution — Cortex-M4 (Thumb-2 IT)

cmp r0, r1
it lt
movlt r2, #1 ; if (r0 < r1) r2 = 1; else nothing

분기 없이 conditional move를 수행하여 control hazard를 회피합니다. 짧은 if-then 패턴에 최적입니다.

; 회피 (branch hazard)
cmp r0, r1
bge skip
mov r2, #1
skip:
; Good (no branch)
cmp r0, r1
it lt
movlt r2, #1

다만 Cortex-A는 IT block 효율이 떨어집니다. 그래서 컴파일러가 자동으로 판단합니다.

#NEON·DSP — SIMD로 Latency Hiding

; 4 element 합산 — scalar
add r0, r1, r2
add r0, r0, r3 ; RAW chain (4 cycle)
add r0, r0, r4
add r0, r0, r5
; NEON SIMD
vadd.f32 q0, q1, q2 ; 4 elements 동시 — 1 cycle

SIMD는 수평적 병렬화이므로 RAW chain을 우회합니다.

#자주 하는 실수

⚠️ -O0에서 stall 측정

Terminal window
gcc -O0 -o test test.c
# 의미 없음 — 컴파일러가 명령 재정렬 안 함, 결과 inconsistent

성능 측정은 최소 -O2에서 해야 합니다.

⚠️ volatile로 모든 변수 표시

volatile uint32_t counter; // ← 모든 access fence
counter++; // load + add + store, 재정렬 금지

성능 critical loop에서 volatile컴파일러 최적화를 차단합니다. register와 통신 register에만 volatile을 씁니다.

⚠️ Branch가 항상 stall이라 가정

Modern CPU에서는 predict가 성공하면 stall = 0입니다. Branch 자체가 문제가 아니라 misprediction이 문제입니다.

⚠️ Forwarding 의존성 무시

for (int i = 0; i < N; i++) {
x = x + a[i]; ; ← RAW chain — 한 cycle 1 add만
}

Loop unroll로 독립 accumulator를 만듭니다.

for (int i = 0; i < N; i += 4) {
x0 += a[i];
x1 += a[i+1];
x2 += a[i+2];
x3 += a[i+3];
}
sum = x0 + x1 + x2 + x3;

#정리

  • Stall은 pipeline bubble이며, IPC 손실로 이어집니다.
  • RAW (data dependency)는 forwarding으로 해결합니다.
  • Load-use는 forwarding이 불가하여 1-2 cycle penalty가 있습니다.
  • PMU STALL_FRONTEND와 STALL_BACKEND로 원인을 추정합니다.
  • 컴파일러 -O2, loop unroll, SIMD로 stall을 회피합니다.

다음 편은 Branch Prediction입니다.

#관련 항목

Embedded Performance Engineering · 11 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 처리량 회복