C 코드 최적화의 필요성

- 임베디드 CPU 한계 : 저전력, 저가로 성능 제한

 -> 제한된 자원으로 좋은 성능을 내기위해 필요

+ CPU 구조, 어셈블러, 컴파일러에 대한 이해 필요

 

일반적인 최적화 방법

- 나눗셈, 나머지 연산을 피함

- 반복을 최적화

- 메모리 보다 빠른 레지스터 사용

- 함수 호출 방식을 최적화

 

AVR core 구조

- 폰노이만 : 데이터, 프로그램 메모리를 하나의 버스로 사용

- 하버드 : 데이터, 프로그램 메모리를 분리 -> 동시에 명령어와 데이터를 읽고쓰기 가능

 -> AVR core는 하버드 아키텍처를 따름

 

CPU 연산 과정

- fetch :  메모리서 명령어 가져옴

- decode : 명령어에 있는 레지스터 해석

- execute : 실행

 

AVR core

- single level pipelining 사용

- 한 명령어가 실행되는 동안 다음 명령어가 pre fetch됨

 -> 매 클럭 사이클마다 명령어가 실행

- 32개의 8비트 범용 레지스터를 가지고 있음. 이를 잘 활용해서 최적화

- R0 ~ R25 : 데이터 레지스터로 사용

- R26 ~ R31 : 주소 레지스터로 사용

 

 

 

AVR 컴파일러

- AVR studio : gcc 사용. avr-gcc

- code vision : 상용 컴파일러 사용

 

 

AVR GCC 최적화 옵션

- 옵션에 따라 코드 크기, 성능 최적화 레벨 지정 가능

  ex) -O0, -O1, -O2, -O3, -Os

    => avr-GCC -O3 main.c

  *-O0 는 최적화 x, -O3은 가장 높은 성능, -Os는 코드 사이즈 가장 줄임

 

 

 

데이터에 따른 최적화

- 타입에 따른 최적화 : 가능한 크기가 작은 타입 사용

- 범위에 따른 최적화 : 같은 크기더라도 전역보다 지역변수가 유리.

* atmega128에선 전역변수는 SRAM(16비트 어드레스모드)에 할당.

   -> 변수 크기가 8비트 크기여도 16비트 크기 어드레스로 할당하여 공간 낭비

 

static을 이용한 최적화

- 전역 변수에 static 사용시 선언된 파일 내에서만으로 범위 제한.

- 지역 변수의 경우 static 사용 안하는것이 좋다.

 

어셈블리어를 사용한 최적화

- 적당히 사용하면 좋으나 코드 이식성을 떨어지게함.

- 인라인 어셈블리 : C코드 일부를 어셈블리어로 사용

ex : #define sei() __asm__ __volatile__("sei"::)

- __asm__ : 인라인 어셈블리를 의미하는 지시어

- __volatile__ : 최적화에 의해 없어지는걸 방지

- sei : SREG 레지스터의 I flag(전역 인터럽트 플레그)를 셋

  일반 인라인 어셈블리
C코드 #include <avr/io.h>
void enable_usart_rx(void)
{
  UCSR0B |= 0x80;
};

int main()
{
  enable_usart_rx();
  while(1){
  }
}
#include <avr/io.h>
#define enable_usart_rx() \
  __asm__ __volatile__(\
  "lds r24, 0x00C1" "\n\t" \
  "ori r24, 0x80" "\n\t" \
  "sts 0x00C1, r24" \
  ::)
int main()
{
  enable_usart_rx();
  while(1){
  }
}
AVR 코드 메모리 사용량 296bytes 226bytes
최적화 옵션 -Os -Os

 

Cycle Counter

- 어셈블리 명령어가 실행된 회수

- 실행이 끝난 후 값 = 총 실행된 명령어 회수

 

+ Recent posts