승코딩당당당
[최적화] 임베디드 C 코드 최적화 전략 정리 본문
📌 개요
임베디드 시스템은 제한된 메모리와 낮은 연산 자원을 기반으로 동작하는 경우가 많기 때문에, 동일한 기능을 구현하더라도 코드를 어떻게 작성하느냐에 따라 성능과 자원 사용량이 크게 달라진다. 특히 MCU 환경에서는 ROM과 RAM이 모두 한정되어 있고, 실행 속도 또한 시스템 안정성과 직결되기 때문에 C 코드 최적화는 선택이 아니라 필수에 가깝다.
이번 글에서는 임베디드 C 코드 최적화를 메모리 관점, 실행 속도 관점, 데이터 처리 관점, 흐름 제어 관점으로 나누어 정리하였다. 단순히 “빠르게 만드는 방법”이 아니라, 메모리 절약과 실행 속도 향상 사이의 균형을 어떻게 잡아야 하는지에 초점을 두고 살펴본다. 또한 자료형 선택, 타입 변환 최소화, 비트 연산 활용, 루프 최적화, 인라인 함수 사용 등 실제 현장에서 자주 적용되는 기법들을 체계적으로 정리하였다.
임베디드 C 코드 최적화
메모리관점에서의 C코드 최적화
- ROM 최적화
- 프로그램 코드, 상수, 초기화된 전역 변수와 정적 변수는 ROM 영역에 저장된다.
- 코드와 상수를 줄이는 것이 핵심이다.
- 방법
- 데드 코드 제거
- 매크로나 인라인 함수의 무분별한 사용 지양 (코드 크기 증가 가능)
- 불필요한 전역 변수 초기화 제거
- 상수나 전역 변수 대신 지역 변수 사용
- 표현을 간결하게 작성하고 불필요한 중간 연산 생략
- 표준 라이브러리 사용 최소화 (코드 크기 증가 가능)
- RAM 최적화
- RAM에는 초기화되지 않은 전역 변수, 정적 변수, 지역 변수, 함수 인자, 함수 호출 시 발생하는 context(Stack/Heap)가 포함된다.
- Stack 사용량을 줄이는 것이 중요하다.
- 방법
- 함수 호출 깊이를 줄인다.
- 작은 함수는 매크로나 인라인으로 대체 (Stack 사용 감소 목적)
- 구조체나 배열 대신 포인터 활용
- 비트 플래그 활용
- 값이 변하지 않는 전역 변수는 const로 선언
실행속도 관점에서의 C코드 최적화
- 실행시간을 줄이는 최적화
- 인라인 함수 사용
→ 함수 호출 오버헤드 제거 (단, 코드 크기 증가 가능) - 참조 테이블(Lookup Table) 활용
- 인라인 어셈블리 활용
- 전역 변수 사용
→ 함수 호출 시 파라미터 전달 오버헤드 감소
(단, 모듈화 저하 가능) - 폴링 방식 활용
→ 인터럽트 대비 오버헤드가 적은 경우 존재 - 정수 연산 활용
→ 부동소수점 연산을 고정소수점 또는 정수 연산으로 대체
- 인라인 함수 사용
실행속도 vs 메모리 관점에서의 C코드 최적화
- 실행속도를 높이는 최적화와 메모리를 절약하는 최적화는 서로 충돌할 수 있다.
- 예: 인라인 함수는 실행속도는 증가시키지만 코드 크기는 증가
- 시스템 요구사항에 맞춰 균형을 잡는 것이 중요하다.
Data Handling (데이터 처리)
Data handling 관련 방법들
- Data Types Usage
- Avoid Type Conversion
- Unsigned Advantage
- Float & Double
- Constant & Volatile
- Data Alignment – Arrangement & Packing
- Pass by Reference
- Others
Data type 사용 / Type conversion
- Data Types Usage
- 적절한 Data Type 선택은 재귀 연산이나 대규모 배열 처리에서 매우 중요하다.
- 불필요하게 큰 타입을 사용하면 메모리와 처리 시간이 증가한다.
- 예) 변수 범위가 0~200이면 unsigned char 사용이 적절하다.
- Avoid Type Conversion
- 가능한 동일한 타입의 변수끼리 연산하도록 설계한다.
- 타입 변환은 추가적인 CPU 사이클을 소모한다.
- Signed와 Unsigned는 서로 다른 타입으로 간주된다.
Signed & Unsigned / Float & Double
- Signed & Unsigned 구분
- Unsigned: 몫/나머지 연산, loop counter, 배열 인덱싱
- Signed: 음수 연산이 필요한 경우
- Float & Double
- Float 최대값: 0x7f7fffff
- Double 최대값: 0x7f7fffffffffffff
- float 상수는 ‘f’를 명시하여 불필요한 double 변환 방지
예) x = y + 0.2f;
Constant와 volatile
- Constant
- const로 선언하면 ROM에 저장된다.
- 상수 데이터를 RAM에 중복 저장하지 않도록 하기 위함이다.
- 읽기 전용 데이터는 반드시 const 처리한다.
- Volatile
- 컴파일러의 최적화를 방지한다.
- 인터럽트에 의해 변경되는 변수, I/O 레지스터에 사용한다.
- 항상 메모리에서 값을 읽도록 보장한다.
Data Alignment - Arrangement & Packing
- 구조체 설계 시 메모리 정렬 고려
- 32비트 MCU에서는 32비트 단위 정렬이 효율적
- char 등 작은 타입은 묶어서 선언하면 패딩 최소화 가능
Pass by Reference
- 함수 인자가 많아질수록 push/pop 동작이 증가한다.
- 구조체를 포인터로 전달하면 오버헤드를 줄일 수 있다.
Initialization
- 적절한 메모리 할당 방식 사용 시 RAM 사용량 감소 가능
- 초기화 전략은 메모리 배치에 큰 영향을 준다.
Return 값
- 함수의 Return 값은 레지스터에 저장된다.
- 반환값이 필요 없다면 void로 선언하여 불필요한 오버헤드를 줄인다.
비트 플래그 사용
- 상태 플래그를 char로 선언하면 최소 1byte 사용
- 여러 상태는 비트 단위로 묶어 관리하면 메모리 절약 가능
Flow Control Handling (흐름제어 처리)
Flow Control Handling 관련 방법들
- Use of switch statement
- Inline function
- Loop unrolling
- Loop Hoisting
- Loop Overhead
- Others
If vs switch
- 하나의 변수로 분기할 경우 switch가 효율적
- switch는 lookup table 방식으로 동작 가능
- if는 조건에 따라 실행 시간이 달라질 수 있음
Inline 코드 사용
- 함수 호출을 코드 복사로 대체
- 호출 오버헤드 제거
- 작은 함수에서 효과적
- 큰 함수는 코드 크기 증가 문제 발생
Loop Hoisting
- 루프 내부 계산을 외부로 이동
- 반복 연산 감소 → 실행 시간 단축
Loop overhead
- MCU는 0 비교 분기에 최적화된 경우가 많음
- “0이 아닌지 검사”가 특정 값 비교보다 효율적
If - else
- 불필요한 else는 제거
함수 인자의 개수 제한
- 함수 인자는 4개 이내 권장
- 그 이상은 stack 사용 → 성능 저하 가능
Other Handling (기타 처리)
Other Handling 관련 방법들
- Use of operators
- Use formula
- Inline Assembly
- Fixed point & Floating point
- Others
Use of operators
- 기본 연산자를 효율적으로 활용
Replacing: Integer Division with Multiplication
- 정수 나눗셈은 가장 느린 연산 중 하나
- 가능한 경우 곱셈으로 대체
- 오버플로 발생 여부 반드시 고려
Use of Formula
- Horner’s Rule 적용
- 인수분해 활용
- 덧셈으로 곱셈을 대체 가능한 경우 고려
Inline assembly
- asm 키워드를 활용하여 어셈블리 코드 삽입
- 속도 향상 목적
- C와 어셈블리를 혼합하여 사용 가능
부동소수점 연산 제거
- 실수 대신 정수/고정소수점 사용 권장
- 예) 평균 계산 후 마지막 단계에서만 변환 수행
조건문 최적화
- 조건문 대신 bit 연산 활용 가능
📌 마무리 정리
임베디드 C 코드 최적화는 단순히 코드를 줄이거나 빠르게 만드는 기술이 아니라, 시스템 자원을 전략적으로 사용하는 설계 능력과 연결된다.
- ROM을 줄이기 위한 최적화
- RAM 사용량을 최소화하는 설계
- 실행 시간을 단축하는 구조
- 불필요한 연산과 타입 변환 제거
- 인터럽트, I/O, 비트 연산 등 하드웨어 특성을 고려한 코드 작성
이러한 요소들은 서로 영향을 주며 때로는 상충하기도 한다. 예를 들어 인라인 함수는 속도를 높일 수 있지만 코드 크기를 증가시킬 수 있다. 따라서 최적화는 “무조건 빠르게”가 아니라, 시스템 요구사항에 맞는 균형점(Balance)을 찾는 과정이다.
결국 좋은 임베디드 코드는
✔ 자원을 낭비하지 않고
✔ 예측 가능한 실행 시간을 가지며
✔ 하드웨어 특성을 이해하고 작성된 코드이다.
최적화는 마지막 단계에서 억지로 하는 작업이 아니라, 설계 단계부터 고려되어야 하는 기본 역량임을 기억하는 것이 중요하다.
'개발 > 임베디드' 카테고리의 다른 글
| [AUTOSAR] OS Task 기반 LED 제어 실습 (TRK-MPC560XB) (0) | 2026.03.20 |
|---|---|
| [AUTOSAR] AUTOSAR 기반 소프트웨어 플랫폼과 OS 구조 정리 (0) | 2026.03.20 |
| [메모리 구조] 변수 선언 방식에 따른 메모리 영역 확인 (0) | 2026.02.26 |
| [TC275] 4자리 FND 카운터 구현하기 (스위치 제어) (0) | 2026.02.23 |
| [TC275] STM 인터럽트 기반 신호등 구현하기 (1) | 2026.02.23 |