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

SIMD·NEON 활용 — 128-bit Vector·Auto-Vectorization·SVE/SVE2

· Hawk · 4분 읽기

#한 줄 요약

**“SIMD = 한 명령으로 여러 데이터”**입니다. 4배 speedup이 흔합니다.

#ARM NEON — 128-bit Vector

Cortex-A 시리즈의 표준입니다. 128-bit register 32개(v0-v31)를 제공합니다.

NEON 128-bit register의 다양한 해석 — 4xf32, 8xi16, 16xi8 등

#Auto-Vectorization — 첫 시도

Terminal window
gcc -O3 -mfpu=neon -ftree-vectorize -ftree-vectorizer-verbose=2 source.c
void 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 못 함
}
// Good
void 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_f324 float load
vst1q_f324 float store
vaddq_f324 float add
vmulq_f324 float mul
vfmaq_f32fused multiply-add
vdupq_n_f32scalar → 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

// Scalar
for (i = 0; i < N; i++) c[i] = a[i] + b[i];
// → 1 add per cycle (Cortex-M)
// NEON
float32x4_t va = vld1q_f32(...);
// → 4 add per cycle
// 이론 4x — 실측 3.2-3.8x (load/store가 병목)

perf 또는 DWT CYCCNT로 측정합니다.

#Reduction Pattern

// 회피 — RAW chain
float sum = 0;
for (i = 0; i < N; i++) sum += a[i]; // 1 add/cycle (RAW)
// Good — 4-way reduction
float32x4_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로 확인합니다.

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