주어진 환경에서 코드를 최적화시켜 생산 비용을 낮출 수 있는 방법을 제시한다.
거의 모든 임베디드 장비에 들어가는 것이 바로 RAM 과 ROM 이다. 그 만큼 중요한 데, 점점 하드웨어의 가격이 낮아지고 있기 때문에 예전만큼 최적화에 대한 압박은 줄었다.
하지만, 최적화에 대한 관심만큼은 점점 높아지고 있다. 여기서는 크게 RAM 최적화와 ROM 최적화에 대해 알아본다.
그전에 C 프로그램이 어떻게 RAM 또는 ROM 에서 동작하는지 알아보도록 하겠다.

들어가기 전에

프로그램을 ROM 에서 실행하면 RAM 에서 실행할 때 보다 속도가 떨어진다. RAM 이 넉넉하고 실행속도가 중요하다면 RAM 에서 실행하는 것이 더 좋다. 프로그램을 RAM 에서 실행할 때만 텍스트 섹션을 RAM 에 로드한다.
메모리의 특성상 프로그램 런타임에 변하지 않는 코드들이 ROM 에 저장되고, 런타임에 값이 변하는 것들은 RAM 에 저장된다.

ROM 은 텍스트와 데이터 세그먼트로 구성된다. 텍스트 세그먼트에는 프로그램 코드와 상수가 저장된다.
데이터 세그먼트에는 초기화된 전역변수와 정적변수가 저장된다.

  • ROM : 프로그램 코드와 상수, 초기화된 전역 변수와 정적 변수들
  • RAM : ROM 의 초기화된 전역 변수와 정적 변수들의 복사본, 초기화 되지 않은 전역 변수와 정적 변수, 지역 변수, 함수의 인자 및 함수 호출시 발생하는 문맥

이상한 점은 위의 그림을 보면 데이터 세그먼트는 ROM 에 있는데, 전역 변수와 정적 변수는 말 그대로 값이 변할 수 있는 변수이다.
그런데 이값을 ROM 에 저장한다면 런타임에 변한 값이 적용되지 않고, 계속해서 초기값만을 갖고 있게 될 것이다. 그래서 데이터 세그먼트를 RAM 에 복사해서 런타임 시 값의 변동을 저장할 수 있게 한다.

만약 속도를 위해 프로그램을 RAM 에 로딩해서 실행한다면 이 과정을 생략한다.
데이터 세그먼트에는 ROM 의 데이터 세그먼트에서 복사된 초기화된 전역 변수와 정적 변수가 저장되어 있다.
bss 세그먼트는 스타트업(startup) 코드에 의해서 0 으로 자동 초기화된다. 전역 변수와 정적 변수를 프로그램 내에서 초기화하지 않아도 0 으로 자동 초기화되는 이유가 이 때문이다.
변수들의 저장 위치를 정리해보면 다음과 같다.

  1. 초기화 되는 전역 변수와 정적 변수 → 데이터 세그먼트
  2. 초기화되지 않은 전역 변수와 정적 변수 → bss 세그먼트
  3. 지역 변수와 함수 인자 → 스택
  4. 레지스터 변수 → 레지스터
  5. 문자열과 상수 → 텍스트 세그먼트

다음은 각 변수의 종류별 특징을 표로 나타냈다.

지역변수 전역변수 정적변수 레지스터 변수
지정자 auto(생략가능) extern(생략가능) static register
메모리 스택 데이터 세그먼트 데이터 세그먼트 레지스터
선언 함수 내부 어디든 가능 지역(함수 내부), 전역(함수 외부) 함수 내부
초기화 초기화 문장을 통해서만 실행, 초기화 안하면 쓰레기값 초기화 문장 없이도 0 으로 자동 초기화 초기화 문장 없이도 0 으로 자동 초기화 초기화 문장을 통해서만 실행, 초기화 안 하면 쓰레기값
유효범위 선언한 함수나 블록 내부 함수 외부, 파일 외부 함수 내부, 함수 외부, 파일 내부 선언한 함수나 블록 내부
유효시간 선언한 함수가 실행될 때부터 종료될 때까지 프로그램 실행될 때부터 종료될 때까지 프로그램 실행될 때부터 종료될 때까지 선언된 함수가 실행될 때부터 종료될 때까지

ROM 최적화

ROM 영역에 저장된 요소들(코드 + 상수 + 초기화된 전역변수)은 모두 하나의 실행 파일이다.
임베디드 환경에서는 HDD 가 없기 때문에 실행 파일을 ROM 에 저장하고 런타임에 사용되는 나머지 섹션들은 RAM 에 저장해서 프로그램을 실행한다. 때로는 프로그램의 속도를 높이려고 실행 파일을 RAM 에 로딩하여 실행시킬 수도 있다.
ROM 을 최적화 하려면 결국 실행파일을 줄여야 한다. 다음은 최적화를 위한 여러가지 방법들이다.

사용되지 않는 계산이나, 도달하지 않는 코드를 제거한다. 컴파일러의 최적화 옵션을 사용해서 할 수 있지만, 자칫 만드시 필요한 코드를 컴파일러 마음대로 제거하는 것을 방지하기 위해 'volatile' 를 사용하여 최적화를 방지할 수 있다.

속도 최적화의 경우에는 필요하지만, 크기 최적화 시에는 코드의 크기가 증가하므로 피한다.

전역 변수나 정적 변수를 초기화하여 사용하면 ROM 과 RAM, 두 메모리에 모두 저장된다. 그래서 ROM 을 절약하려면 전역 변수나 정적 변수의 초기화를 생략하는 것이 좋다. 초기화를 안하면 bss 세그먼트에 저장된다. bss 세그먼트는 RAM 에 위치한다.

전역 변수를 지역 변수로 대체하는 것도 ROM 을 절약하는 한 방법이다. 지역 변수는 스택에 저장되기 때문에 ROM 사용을 줄일 수 있다. 마찬가지로 상수 역시 코드 크기를 늘리므로 변수로 대체한다.

printf() 와 같은 표준 라이브러리 함수들은 많은 조건들에 대비한 코드들이 많기 때문에 코드 크기가 증가하는 경향이 있다.
특히 임베디드 소프트웨어는 타겟 프로세서에 연결된 주변기기가 각 시스템마다 다르기 때문에 표준 라이브러리 함수보다는 직접 만들어 사용하는 경우가 많다.

RAM 최적화

함수 호출이 많아 질 수록 메모리의 스택 사용량이 늘어나기 때문이다.

함수 대신 매크로나 인라인을 사용해서 RAM 을 절약할 수 있다.

구조체는 대부분 크기가 큰 편이다. 함수의 인자나 리턴 값이 구조체일 때, 값에 의한 호출을 한다면, 메모리나 속도의 손실이 크다. 그러므로 참조에 의한 호출을 사용하는 것이 좋다.

현재 프로그램 상태를 나타내는 데 플래그를 많이 사용한다. 예를 들어 인터럽트 발생 여부를 나타내거나, 응용프로그램에서 데이터를 검색했을 때, 검색 여부를 나타내는 플래그가 필요하다. 이러한 플래그는 참, 거짓만을 나타내기 때문에 1 비트로 충분히 표현 가능하다. 하지만 C 언어에서는 boolean 형 데이터 타입이 없으므로 보통 int 형 변수를 사용한다.
이러한 방법은 프로그램에서 많이 사용되지만, 메모리 낭비가 크다. 0 과 1을 표현하기 위해서 1 비트로 충분한 데, 32 비트를 사용하기 때문이다.

위의 그림을 보면, ROM 에서 데이터 세그먼트만 복사되는 것처럼 나와있는데, 텍스트 세그먼트의 경우에 RAM 에 텍스트 세그먼트를 할당할 수도 있고 안할 수도 있다.
텍스트 세그먼트가 항상 RAM 에 복사될 필요는 없다. 하지만 메모리의 특성상 ROM 보다 RAM 의 속도가 빠르므로 프로그램의 성능을 높이려고 코드를 RAM 으로 로드할 수 있다. 하지만, 이 경우에는 그만큼 RAM 을 더 사용하게 되므로 RAM 을 아껴야 하는 경우에는 속도가 좀 느리더라도 RAM 으로 로드하지 않는 것이 좋다.

  • computer/embedded/코드_최적화하기.txt
  • Last modified: 3 years ago
  • by likewind