SIMD·NEON 활용 — 128-bit Vector·Auto-Vectorization·SVE/SVE2
#한 줄 요약
**“SIMD = 한 명령으로 여러 데이터”**입니다. 4배 speedup이 흔합니다.
#ARM NEON — 128-bit Vector
Cortex-A 시리즈의 표준입니다. 128-bit register 32개(v0-v31)를 제공합니다.
#Auto-Vectorization — 첫 시도
gcc -O3 -mfpu=neon -ftree-vectorize -ftree-vectorizer-verbose=2 source.cvoid scale(float *a, float k, int N) { for (int i = 0; i < N; i++) { a[i] *= k; }}-O3를 켜면 컴파일러가 자동으로 NEON fmul.f32 q0, q1, q2로 4-way 처리합니다.
조건은 다음과 같습니다.
- 명확한 stride가 있어야 합니다 (보통 1).
- Alias가 없어야 합니다 (
restrict키워드가 도움이 됩니다). - Branch가 없어야 합니다.
- 길이가 vector width의 배수이거나 epilogue로 처리 가능해야 합니다.
#restrict로 vectorizer 도움
// 회피void add(float *a, float *b, float *c, int N) { for (int i = 0; i < N; i++) c[i] = a[i] + b[i]; // 컴파일러: a와 c가 alias할 수도 → vector 못 함}
// Goodvoid add(float * restrict a, float * restrict b, float * restrict c, int N) { for (int i = 0; i < N; i++) c[i] = a[i] + b[i];}#NEON Intrinsics로 직접 작성
#include <arm_neon.h>
void add_neon(float *a, float *b, float *c, int N) { int i; for (i = 0; i + 4 <= N; i += 4) { float32x4_t va = vld1q_f32(&a[i]); float32x4_t vb = vld1q_f32(&b[i]); float32x4_t vc = vaddq_f32(va, vb); vst1q_f32(&c[i], vc); } /* Tail */ for (; i < N; i++) c[i] = a[i] + b[i];}자주 쓰는 intrinsic은 다음과 같습니다.
| Intrinsic | 동작 |
|---|---|
vld1q_f32 | 4 float load |
vst1q_f32 | 4 float store |
vaddq_f32 | 4 float add |
vmulq_f32 | 4 float mul |
vfmaq_f32 | fused multiply-add |
vdupq_n_f32 | scalar → 4 element broadcast |
#실전 — Dot Product
float dot(const float *a, const float *b, int N) { float32x4_t sum = vdupq_n_f32(0.0f); int i; for (i = 0; i + 4 <= N; i += 4) { float32x4_t va = vld1q_f32(&a[i]); float32x4_t vb = vld1q_f32(&b[i]); sum = vfmaq_f32(sum, va, vb); // sum += a * b } /* Horizontal sum */ float32x2_t h = vadd_f32(vget_low_f32(sum), vget_high_f32(sum)); h = vpadd_f32(h, h); float result = vget_lane_f32(h, 0);
for (; i < N; i++) result += a[i] * b[i]; return result;}Scalar 대비 3-4배 빠릅니다.
#Helium (MVE) — Cortex-M 용 SIMD
Cortex-M55와 M85에서는 MVE(M-profile Vector Extension)를 제공합니다.
#include <arm_mve.h>
void add_mve(int16_t *a, int16_t *b, int16_t *c, int N) { for (int i = 0; i < N; i += 8) { int16x8_t va = vld1q(&a[i]); int16x8_t vb = vld1q(&b[i]); int16x8_t vc = vaddq(va, vb); vst1q(&c[i], vc); }}NEON과 다른 점은 다음과 같습니다.
- Beat scheme: 4 beat를 한 cycle씩 처리합니다 (low power).
- Predication: tail handling을 자동으로 처리합니다.
- 레지스터가 8개뿐입니다 (NEON은 32개).
주로 DSP, 오디오, ML inference에 사용합니다.
#SVE — 가변폭 SIMD
Cortex-A510·A78·X1 등에 SVE 또는 SVE2가 들어 있습니다. 폭은 구현에 따라 128 ~ 2048 bit으로 다양합니다.
#include <arm_sve.h>
void add_sve(float *a, float *b, float *c, int N) { int i = 0; svbool_t pg = svwhilelt_b32(i, N); while (svptest_first(svptrue_b32(), pg)) { svfloat32_t va = svld1(pg, &a[i]); svfloat32_t vb = svld1(pg, &b[i]); svst1(pg, &c[i], svadd_z(pg, va, vb)); i += svcntw(); pg = svwhilelt_b32(i, N); }}Predication(mask)으로 tail handling이 자동으로 됩니다. 길이를 모르는 loop도 안전합니다.
같은 binary가 128-bit, 256-bit SVE 양쪽에서 그대로 동작합니다.
#측정 — IPC와 Throughput
// Scalarfor (i = 0; i < N; i++) c[i] = a[i] + b[i];// → 1 add per cycle (Cortex-M)
// NEONfloat32x4_t va = vld1q_f32(...);// → 4 add per cycle
// 이론 4x — 실측 3.2-3.8x (load/store가 병목)perf 또는 DWT CYCCNT로 측정합니다.
#Reduction Pattern
// 회피 — RAW chainfloat sum = 0;for (i = 0; i < N; i++) sum += a[i]; // 1 add/cycle (RAW)
// Good — 4-way reductionfloat32x4_t acc0 = vdupq_n_f32(0);float32x4_t acc1 = vdupq_n_f32(0);float32x4_t acc2 = vdupq_n_f32(0);float32x4_t acc3 = vdupq_n_f32(0);
for (i = 0; i + 16 <= N; i += 16) { acc0 = vaddq_f32(acc0, vld1q_f32(&a[i])); acc1 = vaddq_f32(acc1, vld1q_f32(&a[i+4])); acc2 = vaddq_f32(acc2, vld1q_f32(&a[i+8])); acc3 = vaddq_f32(acc3, vld1q_f32(&a[i+12]));}/* 16-way ILP — load + add latency 가림 */#Memory Alignment
__attribute__((aligned(16))) float a[1024];float32x4_t v = vld1q_f32(a); // ← aligned load 빠름NEON은 misaligned 접근도 가능하지만, 정렬해 두면 10-20% 더 빠릅니다. Cortex-M MVE는 정렬을 권장합니다.
#SIMD 적용 어려운 경우
- Branch가 많은 경우: predicated 명령으로 회피합니다.
- Indirect access: gather/scatter를 써야 하는데 SVE2만 지원합니다.
- Cross-element dependency: prefix sum 같은 recurrence가 어렵습니다.
- Bit-level operation: bit manipulation은 vector에 친화적이지 않습니다.
#자주 하는 실수
⚠️ Auto-vectorize 신뢰
gcc -O3라고 해서 항상 vectorize되는 것은 아닙니다. -fopt-info-vec로 확인합니다.
gcc -O3 -fopt-info-vec -c src.c# loop vectorized using 16 byte vectors ← 성공# loop turned into non-loop ← 다른 최적화Vectorize가 안 되었다면 intrinsics를 쓰거나 OpenMP #pragma omp simd를 적용합니다.
⚠️ Tail handling 누락
for (i = 0; i < N; i += 4) { // N=10이면 i=8까지 → tail 2 남음 process_4(arr + i);}tail은 scalar로 처리하거나 SVE predicate으로 마무리합니다.
⚠️ Mixed precision 무시
int16_t a[N]; float b[N];for (i) b[i] = (float)a[i] * 2.0f; // ← conversion 비쌈NEON vcvtq_f32_s16를 명시하거나, 아예 fixed-point로 유지합니다.
⚠️ FP exception 가정
NEON에서는 NaN/Inf 동작이 IEEE-754 flush-to-zero 모드일 수 있습니다. 정밀 수치 코드를 다룬다면 주의가 필요합니다.
#정리
- ARM NEON은 128-bit, 4 × float 구조입니다.
- Auto-vectorize는 조건이 까다롭습니다.
-O3 -ftree-vectorize와 restrict를 함께 씁니다. - Intrinsics로 직접 작성하면 확실하게 통제할 수 있습니다.
- Cortex-M55 이상은 Helium MVE를 지원합니다.
- 모던 Cortex-A는 SVE/SVE2로 가변폭을 제공합니다.
- Reduction은 multiple accumulator로 RAW chain을 회피합니다.
다음 편에서는 PMU를 다룹니다.
#관련 항목
Embedded Performance Engineering · 18 of 57
- 1 Embedded 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
- 10 CPU 파이프라인 분석 — 5-stage·Cortex-M·Cortex-A 비교
- 11 Pipeline Stall 분석 — Data·Structural·Control Hazard·Forwarding
- 12 Branch Prediction 분석 — Static·2-bit·BTB·BHT·Mispredict 비용
- 13 Speculative Execution 분석 — OoO·Reorder Buffer·Register Renaming
- 14 CPU Cache 기초 — L1·L2·L3·Set Associative·Replacement Policy
- 15 Cache Miss 3C Model 분석 — Compulsory·Capacity·Conflict
- 16 Cache Line 최적화 — Alignment·Prefetch·False Sharing 처리
- 17 메모리 대역폭 분석 — STREAM·Roofline·Bus Saturation 측정
- 18 SIMD·NEON 활용 — 128-bit Vector·Auto-Vectorization·SVE/SVE2
- 19 PMU·HPM 하드웨어 카운터 분석 — 정밀 성능 진단
- 20 임베디드 Bus Architecture — AHB·AXI·CHI 진화와 5-Channel
- 21 Bus Contention 진단 — Arbitration·QoS·Starvation 측정
- 22 DMA 성능 최적화 — Burst·Scatter-Gather·Chain·Cache 일관성
- 23 DMA vs CPU Copy 성능 비교 — Break-even·Setup Overhead 실측
- 24 Interrupt Latency 분석 — 진입·종료·Tail-Chaining·Late Arrival
- 25 Interrupt Storm 처리 — NAPI·Rate-Limit·Polling 전환
- 26 MMIO 접근 성능 — Cache Policy·Write-Combining·Volatile·Barrier
- 27 Peripheral Clock 분석 — PLL·Divider·Gating·DVFS
- 28 Power vs Performance 트레이드오프 — DVFS·Race-to-Idle·Big.LITTLE
- 29 Thermal Throttling 분석 — Junction Temp·Trip Point·냉각
- 30 CXL Interconnect 분석 — AI 시대 메모리 대역폭 확장
- 31 Concurrency 기초 — Concurrency vs Parallelism·Race·Memory Model
- 32 False Sharing 진단 — Cache Line Ping-Pong·Padding·측정
- 33 Lock Contention 분석 — Wait·Hold·Convoy·측정 기법
- 34 Spinlock 성능 분석 — Spin-Wait vs Context Switch·Ticket·MCS
- 35 Mutex 성능 분석 — Futex·Adaptive·Priority Inheritance
- 36 Reader-Writer Lock 성능 — Reader/Writer Priority·RCU·Seqlock
- 37 Lock-Free 자료구조 성능 — CAS·ABA·Hazard Pointer·Epoch Reclamation
- 38 Memory Ordering 분석 — Acquire·Release·Seq-Cst·ARM Relaxed Model
- 39 Cache Coherency 프로토콜 — MESI·MOESI·Snoop·Directory
- 40 SMP 성능 분석 — Per-Core·Affinity·Load Balance·Scalability
- 41 Linux perf 기초 — stat·record·report 활용
- 42 Linux perf 고급 — Raw Event·Tracepoint·perf script
- 43 ftrace 활용 — function·function_graph·latency tracer
- 44 eBPF·bpftrace 동적 트레이싱 — 커널 무수정 관측
- 45 Flamegraph 분석 — On-CPU·Off-CPU·Differential
- 46 ARM DS·Lauterbach 분석 — Hardware Trace 전문 도구
- 47 Bare-metal 프로파일링 — GPIO·DWT·SysTick·ITM 활용
- 48 NVIDIA 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로 떨어지는 이유
- 55 CXL.mem 지연·대역폭 실측 — Direct·Switch·Pooled 토폴로지 비교
- 56 CXL 성능 프로파일링 도구 — cxl-cli·DAMON·perf-mem 활용
- 57 실전 사례 — CXL.mem 추가로 LLM inference KV cache 처리량 회복
관련 글
실전 사례 — 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 통계.
같은 시리즈에서 이어 읽기
CXL.mem 지연·대역폭 실측 — Direct·Switch·Pooled 토폴로지 비교
CXL.mem 토폴로지별 실측 — Direct attach·Single switch·Multi-host pool의 지연·대역폭 비용 측정.
같은 시리즈에서 이어 읽기
이 글을 참조하는 글 (4)
- PMU·HPM 하드웨어 카운터 분석 — 정밀 성능 진단 — Embedded Performance Engineering
- 메모리 대역폭 분석 — STREAM·Roofline·Bus Saturation 측정 — Embedded Performance Engineering
- ARM NEON 심화 — Matrix Multiply·FFT·Image Filter 적용 — Modern Embedded Recipes
- SIMD 활용 분석 — Intrinsics·Auto-Vectorization·OpenMP SIMD — Modern Embedded Recipes