처리중입니다. 잠시만 기다려주세요.
TTJ 코딩클래스
정규반 단과 자료실 테크 뉴스 코딩 퀴즈
테크 뉴스
Hacker News 2026.04.19 24
#AI

Cortex-M에서 부동소수점 다루기: 작은 칩 속 숨겨진 함정들

Hacker News 원문 보기
Cortex-M에서 부동소수점 다루기: 작은 칩 속 숨겨진 함정들

임베디드의 골칫거리, 부동소수점

ARM Cortex-M 시리즈는 전 세계 마이크로컨트롤러(MCU) 시장의 사실상 표준이에요. STM32, nRF52/53, RP2040(라즈베리 파이 피코), ESP32-S3의 코프로세서까지 전부 Cortex-M 계열이에요. 근데 이 작은 칩에서 float이나 double을 쓰는 순간 예상치 못한 성능 저하나 정밀도 문제를 만나는 개발자가 많아요. Daniel Mangum이 쓴 "Floating Point Fun on Cortex-M Processors"는 이 함정들을 실제 어셈블리 수준까지 파고들며 정리한 글인데, 임베디드 개발자라면 꼭 한 번 읽어볼 만해요.

FPU가 있냐 없냐, 이게 첫 갈림길

Cortex-M 계열이라고 다 같은 게 아니에요. Cortex-M0/M0+/M1/M23은 아예 FPU(Floating Point Unit, 부동소수점 전용 연산 장치)가 없어요. M4/M7/M33은 옵션으로 단정밀도(single precision, float 32비트) FPU가 있고요. M7의 상위 모델, M55, M85 정도가 배정밀도(double precision, double 64비트) FPU까지 지원해요.

FPU가 없는 칩에서 float a = 1.5f * 2.0f;를 쓰면 어떻게 될까요? 컴파일러가 soft-float 라이브러리(보통 GCC의 libgcc나 컴파일러 런타임)를 불러서, 정수 연산만으로 부동소수점을 에뮬레이션해요. 곱셈 한 번에 수십~수백 사이클이 들어요. 반면 하드웨어 FPU가 있으면 1~3 사이클이면 끝나요. 같은 코드가 100배 이상 차이 나는 거예요.

double이 몰래 끼어드는 순간

더 고약한 함정이 있어요. M4처럼 단정밀도만 지원하는 FPU를 가진 칩에서 실수로 double을 쓰면 어떻게 될까요? 하드웨어 FPU는 double을 처리 못 해요. 그래서 컴파일러가 soft-float으로 내려버려요. 문제는 이게 눈에 잘 안 띈다는 거예요.

예를 들어 float x = sinf(0.5);라고 쓰면 괜찮은데, float x = sin(0.5);라고 쓰면(함수명에 f가 빠졌죠) sin은 double을 받아요. 그러면 0.5가 double로 승격되고, sin이 double로 계산되고, 결과가 다시 float으로 내려와요. 중간의 double 연산이 전부 소프트웨어 에뮬레이션이에요. 한 글자 빠뜨려서 100배 느려지는 거예요.

printf("%f", my_float)도 위험해요. C 표준이 %f에 넘긴 float을 double로 승격하게 돼 있거든요. 그래서 printf 한 줄 때문에 바이너리에 double 에뮬레이션 라이브러리가 통째로 링크되기도 해요. 임베디드에서 바이너리 크기 수십 KB가 이 한 줄 때문에 날아가는 사례가 흔해요.

컴파일러 플래그가 알려주는 진실

GCC로 빌드할 때 -mfpu=fpv4-sp-d16, -mfloat-abi=hard 같은 플래그를 넣잖아요. 이게 뭐냐면, FPU의 종류(fpv4-sp는 단정밀도)와 ABI(Application Binary Interface)를 지정하는 거예요. ABI는 함수 호출 시 float 인자를 어느 레지스터로 넘길지를 결정해요. soft는 정수 레지스터, softfp는 정수 레지스터지만 내부는 FPU 사용, hard는 FPU 전용 레지스터(S0~S15)예요.

여기서 ABI 불일치라는 지뢰가 있어요. 프로젝트 본체는 hard로 빌드했는데, 링크하는 외부 라이브러리가 soft로 빌드됐다면요? 링커가 에러를 내거나, 더 나쁘게는 조용히 잘못된 값을 넘겨요. 함수 호출했는데 인자가 엉뚱한 레지스터에 들어가서 쓰레기 값으로 동작해요. 디버깅이 악몽이에요.

lazy stacking이라는 최적화

Cortex-M4/M7의 재미있는 기능 하나가 lazy FP context save예요. 인터럽트가 걸릴 때 FPU 레지스터 전부(S0~S31, 32개나 돼요) 스택에 저장하면 시간이 오래 걸리잖아요. 그래서 하드웨어가 "인터럽트 핸들러가 실제로 FPU를 쓸 때까지 저장을 미뤄"요. 인터럽트가 단순 GPIO 토글이면 FPU 저장을 건너뛰어서 지연시간이 크게 줄어요. 실시간 제어 시스템에선 꽤 중요한 최적화예요.

업계 맥락: 왜 아직도 이 문제를 얘기하나

요즘 엣지 AI가 뜨면서 Cortex-M에서 신경망을 돌리는 TinyML이 대세거든요. TensorFlow Lite for Microcontrollers, CMSIS-NN, STM32Cube.AI 같은 프레임워크가 있는데, 이들이 성능을 짜내기 위해 int8 양자화에 집착하는 이유가 바로 이 부동소수점 비용 때문이에요. float32 연산 대신 int8 정수로 바꾸면 메모리도 1/4, 속도도 훨씬 빨라지고, FPU 없는 M0에서도 돌려요.

Zephyr RTOS, FreeRTOS, NuttX 같은 임베디드 OS들이 태스크 전환 시 FPU 컨텍스트 저장 여부를 태스크 속성으로 구분하는 것도 같은 맥락이에요. FPU를 안 쓰는 태스크엔 시간 낭비할 필요가 없거든요.

한국 개발자에게 주는 시사점

국내에는 STM32 기반 제품을 만드는 회사가 많고, 최근엔 nRF52/nRF53로 BLE 제품 만드는 스타트업도 많아요. 이 글의 조언들은 바로 써먹을 수 있어요. 구체적으로는 세 가지만 기억하시면 돼요.

첫째, 부동소수점 리터럴에 꼭 f를 붙이세요. 1.5f1.5는 다른 세상이에요. 둘째, 수학 함수는 float 버전(sinf, cosf, sqrtf)을 쓰세요. 셋째, 빌드 시 -Wdouble-promotion 경고를 켜세요. 의도치 않은 double 승격을 잡아줘요. 이것만 해도 바이너리 크기와 성능이 눈에 띄게 좋아져요.

그리고 한 번쯤은 arm-none-eabi-objdump -d로 핵심 함수의 어셈블리를 열어보세요. vmul.f32, vadd.f32 같은 명령어가 보이면 하드웨어 FPU를 쓰는 거고, bl __aeabi_fmul 같은 함수 호출이 보이면 soft-float이에요. 이 한 번의 확인이 디버깅 시간을 며칠 아껴줘요.

마무리

한 줄로 정리하면, Cortex-M에서 float은 공짜가 아니에요. 컴파일러가 뒤에서 뭘 하는지 알아야 해요. 여러분은 임베디드 프로젝트에서 부동소수점 때문에 고생한 경험 있으신가요? 어떻게 해결하셨는지 이야기 들어보고 싶어요.


🔗 출처: Hacker News

이 뉴스가 유용했나요?

이 기술을 직접 배워보세요

AI 도구, 직접 활용해보세요

AI 시대, 코딩으로 수익을 만드는 방법을 배울 수 있습니다.

AI 활용 강의 보기

"비전공 직장인인데 반년 만에 수익 파이프라인을 여러 개 만들었습니다"

실제 수강생 후기
  • 비전공자도 6개월이면 첫 수익
  • 20년 경력 개발자 직강
  • 자동화 프로그램 + 소스코드 제공

매일 AI·개발 뉴스를 받아보세요

주요 테크 뉴스를 매일 아침 이메일로 전해드립니다.

스팸 없이, 언제든 구독 취소 가능합니다.