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

DMA vs CPU Copy 성능 비교 — Break-even·Setup Overhead 실측

· Hawk · 6분 읽기

#한 줄 요약

“DMA가 항상 빠른 것은 아닙니다.” 작은 transfer에서는 CPU memcpy가 우세합니다.

#CPU memcpy 성능 (Cortex-M4)

memcpy(dst, src, len);

Newlib 또는 컴파일러 inline 버전:

lenCycleBytes/cycle
16121.3
64282.3
256922.8
10243502.9
409614002.9

168 MHz Cortex-M4 기준으로 4 KB copy가 8 µs입니다.

#DMA setup overhead

HAL_DMA_Start(&hdma, src, dst, len);
단계Cycle
Register write (CR·NDTR·PAR·MAR)~30
Enable transfer~5
IRQ handler 진입·종료~50
Wake task (RTOS)~100
Total overhead~200 cycle

200 cycle은 약 60 byte의 CPU memcpy에 해당합니다.

#Break-even Point

CPU cost=N3 cycle,DMA cost=200+N cycle (bus 한계)\text{CPU cost} = \frac{N}{3} \text{ cycle}, \quad \text{DMA cost} = 200 + N \text{ cycle (bus 한계)}

Equal when:N3=200+N    N3N=200    N<0\text{Equal when:} \quad \frac{N}{3} = 200 + N \implies \frac{N}{3} - N = 200 \implies N < 0

즉 단순 cycle 비교만으로는 DMA가 빠를 수 없습니다. 하지만 CPU는 transfer 중에 다른 일을 못 하므로 실제 이득은 CPU offload에서 옵니다.

실제 break-even은 CPU가 할 다른 일에 달려 있습니다. CPU가 idle이면 CPU copy가 빠릅니다.

#실 break-even - CPU가 다른 일 할 때

// Path A: CPU copy, 다른 일 못 함
memcpy(dst, src, 1024); // 350 cycle CPU 점유
do_other_work(); // 그 후 시작
// Path B: DMA + CPU 병행
HAL_DMA_Start(...); // 30 cycle setup
do_other_work(); // 병행 가능
wait_dma(); // DMA 완료 대기 (또는 IRQ)

Path B의 이득은 do_other_work 시간만큼입니다. 작은 transfer는 setup이 곧 끝나서 이득이 적습니다.

Rule of thumb: 64 byte 미만은 CPU, 256 byte 초과는 DMA. 그 사이는 직접 벤치해야 합니다.

#4-byte word memcpy (Cortex-M4 optimal)

void fast_memcpy_word(uint32_t *dst, const uint32_t *src, size_t words) {
for (size_t i = 0; i < words; i++) dst[i] = src[i];
}

Cortex-M4의 ldr/str은 각각 2 cycle로 1.0 byte/cycle 수준입니다.

Loop unroll + LDM/STM:

void fast_memcpy_ldm(uint32_t *dst, const uint32_t *src, size_t words) {
while (words >= 8) {
/* ldmia / stmia — multiple register */
uint32_t r0 = src[0], r1 = src[1], r2 = src[2], r3 = src[3];
uint32_t r4 = src[4], r5 = src[5], r6 = src[6], r7 = src[7];
dst[0] = r0; dst[1] = r1; dst[2] = r2; dst[3] = r3;
dst[4] = r4; dst[5] = r5; dst[6] = r6; dst[7] = r7;
src += 8; dst += 8; words -= 8;
}
while (words--) *dst++ = *src++;
}

8-word burst를 쓰면 bus 효율이 좋습니다.

#ARM Cortex-A NEON memcpy

#include <arm_neon.h>
void neon_memcpy(uint8_t *dst, const uint8_t *src, size_t n) {
while (n >= 64) {
vst4q_u8(dst, vld4q_u8(src)); // 64 byte = 4 × 16-byte vector
src += 64; dst += 64; n -= 64;
}
/* tail */
memcpy(dst, src, n);
}

DDR bandwidth를 saturate시켜서 DMA와 비슷한 속도를 냅니다.

#glibc memcpy - Optimal

glibc memcpy (ARM aarch64)는 다음 기법을 씁니다.

  • SIMD vector load/store
  • Non-temporal (큰 size일 때)
  • Loop unroll 64-byte burst
  • Page-aligned check + dispatch

수십 KB 이상이면 DMA와 동등한 속도가 나옵니다. CPU offload 효과만 다릅니다.

#DMA가 유리한 경우

#1. Peripheral ↔ Memory

HAL_UART_Receive_DMA(&huart, rx_buf, 256);
/* CPU 자유 — 다른 일 가능 */
/* UART byte 도착 시 DMA 자동 transfer */

CPU polling은 CPU 100% blocking이지만 DMA는 CPU 0% (가끔 IRQ만)으로 동작합니다.

#2. 연속 / 주기 transfer

/* Camera frame — 매 frame 30 fps × 1080p × 2 byte = 60 MB/s */
HAL_DCMI_Start_DMA(&hdcmi, MODE_CONTINUOUS, frame_buf, frame_size);

CPU copy로 처리하면 매 frame 33 ms 동안 CPU 100%로 전체 CPU를 점유합니다. DMA는 0%입니다.

#3. Cache pollution 회피

/* 큰 buffer copy — CPU 시 cache evict pressure */
memcpy(dst, src, 1MB);
/* → hot working set 깨짐 */

DMA는 cache를 우회할 수 있고 non-temporal도 지원합니다. 덕분에 CPU가 하는 다른 일의 cache가 보존됩니다.

#CPU가 유리한 경우

#1. Small transfer

memcpy(&header, src, 16); // ← DMA 설정 시간 더 김

#2. 한 번만, CPU idle

load_config(buf, 4096); // boot 시 한 번
/* CPU 어차피 할 일 없음 */

#3. Cortex-M0/M1 (DMA 없거나 1 channel)

/* DMA channel 부족 — UART/SPI 우선 점유 */

#측정 - Benchmark

__DSB(); uint32_t t0 = DWT->CYCCNT;
memcpy(dst, src, len);
__DSB(); uint32_t t1 = DWT->CYCCNT;
printf("CPU: %u cycle\n", t1 - t0);
__DSB(); t0 = DWT->CYCCNT;
HAL_DMA_Start(...);
while (!dma_done) {}
__DSB(); t1 = DWT->CYCCNT;
printf("DMA: %u cycle\n", t1 - t0);

각 size별로 측정해 break-even chart를 만듭니다.

#STM32H743 실측 (170 MHz Cortex-M7 + 1.4 GB/s DDR3)

SizeCPU memcpyDMADMA + CPU 다른 일
32 B28 cycle220 cycle220 - other
256 B200 cycle350 cycle350 - other
4 KB2,800 cycle3,200 cycle3,200 - other
64 KB45,00050,00050,000 - other

CPU offload 효과를 빼면 둘 다 비슷합니다. 다만 CPU가 다른 일을 할 수 있는 상황에서는 DMA가 압승입니다.

#Cortex-A - memcpy 라이브러리

라이브러리특징
glibcNEON + non-temporal + page check
musl작은 코드, 보통 성능
Bionic (Android)SoC-specific 최적화
Cortex-Strings (Arm)per-Cortex 최적

-mtune=cortex-a72로 컴파일하면 적절한 memcpy가 자동으로 선택됩니다.

#DMA 손해 보는 함정

⚠️ 매번 setup

for (i = 0; i < 1000; i++) {
HAL_DMA_Start(...); // ← 매번 200 cycle overhead
wait_dma();
}

one-shot 큰 transferchain transfer로 묶는 것이 좋습니다.

⚠️ CPU가 polling

HAL_DMA_Start(...);
while (DMA->NDTR != 0); // ← CPU 100% busy

IRQ나 task sleep으로 바꿔야 합니다.

⚠️ Buffer alignment 비매칭

uint8_t buf[100]; // 보장 align 1
HAL_DMA_Start(..., buf, ...); // ← word align 안 됨 → split

__attribute__((aligned(32)))로 정렬을 명시해야 합니다.

#자주 하는 실수

⚠️ 작은 copy도 DMA로

HAL_DMA_Start(&hdma, &val, &reg, 4); // ← overkill

ARMv7의 stm은 1 cycle인데 DMA는 200 cycle입니다. register 한 개 쓰기에는 직접 write가 맞습니다.

⚠️ DMA의 cache 영향 무시

큰 transfer DMA에서 non-cacheable을 쓰지 않으면 evict pressure로 working set이 깨집니다.

⚠️ memcpy 대신 직접 loop

for (i = 0; i < N; i++) dst[i] = src[i];

-O2에서는 컴파일러가 알아서 memcpy로 변환합니다. 다만 volatile은 변환되지 않으므로 직접 memcpy(dst, src, N)을 쓰는 편이 안전합니다.

⚠️ DMA throttle

DMA가 너무 빠르면 bus saturation이 일어나 다른 master가 starvation에 빠집니다. bandwidth limiter를 설정해야 합니다.

#정리

  • DMA setup overhead는 60-200 byte 등가의 CPU 작업에 해당합니다.
  • 작은 copy(<64 B)는 CPU, 큰 copy와 peripheral은 DMA가 유리합니다.
  • 진짜 이득은 CPU offload이며 CPU가 다른 일을 할 때만 의미가 있습니다.
  • Cortex-M4 memcpy는 ldmia/stmia 최적화 기준 약 3 byte/cycle입니다.
  • Cortex-A NEON memcpy는 DDR bandwidth를 saturate시킵니다.
  • size별로 직접 측정해서 break-even을 확인해야 합니다.

다음 편은 Interrupt Latency를 다룹니다.

#관련 항목

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