더 많은 플래시와 RAM을 위해 코드를 짜는 방법? [닫은]


14

나는 우리의 특정 제품에 대한 기능을 개발하는 일을했습니다. 동일한 기능을 다른 제품으로 이식하라는 요청이있었습니다. 이 제품은 전통적으로 64K 플래시와 2k RAM을 가진 M16C 마이크로 컨트롤러를 기반으로합니다.

성숙한 제품이므로 132 바이트의 플래시와 2 바이트의 RAM 만 남아 있습니다.

요청한 기능을 포팅하려면 (기능 자체가 최적화되었습니다) 1400 바이트의 플래시와 ~ 200 바이트의 RAM이 필요합니다.

누구나 코드 압축으로 이러한 바이트를 검색하는 방법에 대한 제안이 있습니까? 기존 작업 코드를 압축하려고 할 때 어떤 특정 사항을 찾아야합니까?

모든 아이디어는 정말 감사하겠습니다.

감사.


1
제안 해 주셔서 감사합니다. 나는 당신이 나의 진척 상황을 계속 업데이트하고, 효과가 있었던 단계와 그렇지 않은 단계를 나열 할 것입니다.
IntelliChick

좋아, 여기 내가 시도한 것들이 있습니다 : 컴파일러 버전 위로 이동했습니다. 최적화가 대폭 개선되어 약 2K의 플래시를 얻었습니다. 목록 파일을 검토하여 특정 제품에 대한 중복 및 사용하지 않는 기능 (공통 코드 기반으로 상 속됨)을 확인하고 플래시를 더 확보했습니다.
IntelliChick

RAM의 경우 다음을 수행했습니다. 맵 파일을 통해 가장 많은 RAM을 사용하고있는 기능 / 모듈을 확인했습니다. 레거시 코드의 매우 무거운 기능 (각각 고정 된 양의 메모리가 할당 된 12 개의 채널)을 발견하고 일반적인 채널간에 정보를 공유함으로써 달성하려는 목표를 이해하고 RAM 사용을 최적화했습니다. 이것은 나에게 ~ 200 바이트를 주었다.
IntelliChick

ASCII 파일이 있으면 8 ~ 7 비트 압축을 사용할 수 있습니다. 12.5 % 절약합니다. 압축 파일을 사용하면 압축하는 것보다 압축 및 압축 해제하는 데 더 많은 코드가 필요합니다.
Sparky256

답변:


18

두 가지 옵션이 있습니다. 먼저 중복 코드를 찾아 단일 호출로 이동하여 중복을 제거합니다. 두 번째는 기능을 제거하는 것입니다.

.map 파일을 잘 살펴보고 제거하거나 다시 작성할 수있는 기능이 있는지 확인하십시오. 또한 사용중인 라이브러리 호출이 실제로 필요한지 확인하십시오.

나누기 및 곱하기와 같은 일부 코드는 많은 코드를 가져올 수 있지만 시프트를 사용하고 상수를 더 잘 사용하면 코드가 더 작아 질 수 있습니다. 또한 문자열 상수 및 printfs 와 같은 것을 살펴보십시오 . 예를 들어 각각 printf은 롬을 먹지만 문자열 상수를 반복해서 반복하는 대신 몇 가지 공유 형식 문자열을 가질 수 있습니다.

메모리의 경우 전역을 제거하고 대신 함수에서 자동을 사용할 수 있는지 확인하십시오. 또한 전역 함수와 마찬가지로 메모리를 소비하므로 주 함수에서 가능한 많은 변수를 피하십시오.


1
제안에 감사드립니다. 문자열 상수를 제외하고는 대부분을 시도해 볼 수 있습니다. 순전히 UI가없는 임베디드 장치이므로 코드 내에 printf ()를 호출하지 않습니다. 이러한 제안이 내가 필요로하는 1400 바이트 플래시 / 200 바이트 RAM을 가져 오기를 희망합니다.
IntelliChick

1
@IntelliChick 당신은 얼마나 많은 사람들이 임베디드 디바이스 내부에서 printf ()를 사용하여 디버깅하거나 주변 장치로 전송하는지 인쇄하는 것에 놀랄 것입니다. 당신이 이것보다 더 잘 알고있는 것처럼 보이지만, 누군가 프로젝트 전에 코드를 작성했다면 그것을 확인하는 것이 아프지 않을 것입니다.
Kellenjb

5
그리고 내 이전 의견을 확장하기 위해 얼마나 많은 사람들이 디버깅 문을 추가하지만 결코 제거하지 않는지 놀랄 것입니다. #ifdef를하는 사람들조차도 때때로 게으르다.
Kellenjb

1
정말 고마워요! 나는이 코드베이스를 물려 받았으므로 아픈 사람들은 분명히 그것들을 찾아야합니다. 나는 여러분에게 진행 상황을 게시하고 앞으로이 작업을 수행해야 할 다른 사람들을위한 참고 자료로 내가 얻은 메모리 또는 플래시 바이트 수를 추적하려고 노력할 것입니다.
IntelliChick 5

이것에 대한 질문-중첩 함수 호출이 계층에서 계층으로 홉되는 것은 어떻습니까? 얼마나 많은 오버 헤드가 추가됩니까? 여러 함수 호출을 수행하거나 함수 호출을 줄이고 일부 바이트를 저장하여 모듈성을 유지하는 것이 좋습니다. 그리고 그것은 중요합니까?
IntelliChick 5

8

특정 컴파일러가 특히 나쁜 것을 찾으려면 파일 (어셈블러) 출력을 나열하는 것이 좋습니다.

예를 들어, 지역 변수가 매우 비싸다는 것을 알 수 있으며, 응용 프로그램이 위험 할만한 가치가있을 정도로 단순하면 몇 개의 루프 카운터를 정적 변수로 이동하면 많은 코드가 절약 될 수 있습니다.

또는 배열 인덱싱은 매우 비쌀 수 있지만 포인터 작업은 훨씬 저렴합니다. 혹은 그 반대로도.

그러나 어셈블리 언어를 보는 것이 첫 번째 단계입니다.


3
컴파일러가하는 일을 아는 것이 매우 중요합니다. 내 컴파일러에 어떤 구분이 있는지 확인해야합니다. 아기를 울게합니다 (자체 포함).
Kortuk

8

예를 들어 -OsGCC의 컴파일러 최적화 는 속도와 코드 크기 사이에서 최상의 균형을 제공합니다. -O3코드 크기가 커질 수 있으므로 피하십시오 .


3
이렇게하면 모든 것을 다시 테스트해야합니다! 컴파일러가 만드는 새로운 가정 때문에 최적화로 인해 작업 코드가 작동하지 않을 수 있습니다.
Robert

@Robert, 정의되지 않은 명령문을 사용하는 경우에만 해당됩니다. 예를 들어 a = a ++는 -O0 및 -O3에서 다르게 컴파일됩니다.
Thomas O

5
@ 토마스 사실이 아닙니다. 클록 사이클을 지연시키기위한 for 루프가있는 경우 많은 옵티마이 저는 사용자가 아무것도하지 않는 것을 인식하고 제거합니다. 이것은 단지 하나의 예입니다.
Kellenjb

1
@thomas O, 또한 휘발성 함수 정의에주의해야합니다. 옵티마이 저는 C를 잘 알고 있지만 원자 연산의 복잡성을 이해하지 못한다고 생각하는 사람들을 폭파 할 것입니다.
Kortuk

1
모든 좋은 점. 휘발성 함수 / 변수는 정의에 따라 최적화되어서는 안됩니다. 콜 타임 및 인라인을 포함하여 최적화를 수행하는 옵티마이 저가 손상되었습니다.
Thomas O

8

RAM의 경우 모든 변수의 범위를 확인하십시오-char을 사용할 수있는 int를 사용하고 있습니까? 버퍼가 필요 이상으로 큰가요?

코드 압축은 응용 프로그램 및 코딩 스타일에 따라 다릅니다. 남은 금액은 코드가 약간 짜여져있어 이미 코드가 사라 졌음을 나타냅니다.

또한 전체 기능을 면밀히 검토하십시오. 실제로 사용되지 않고 제압 될 수있는 것이 있습니까?


8

오래된 프로젝트이지만 컴파일러가 이후에 개발 된 경우 최신 컴파일러가 더 작은 코드를 생성 할 수 있습니다.


고마워 마이크! 나는 이것을 과거에 시도했지만 얻은 공간이 이미 사용되었습니다! :) IAR C 컴파일러 3.21d에서 3.40으로 올라갔습니다.
IntelliChick

1
나는 하나 이상의 버전을 옮겼고 그 기능에 맞는 플래시를 더 얻을 수 있었다. 나는 여전히 RAM에 어려움을 겪고있다. :(
IntelliChick

7

공간을 최적화하는 옵션에 대해서는 컴파일러 매뉴얼을 확인하는 것이 좋습니다.

GCC의 경우 -ffunction-sections-fdata-sections--gc-sections링커 플래그는 죽은 코드를 제거에 좋다.

다른 유용한 팁 은 다음과 같습니다 (AVR에 적합).


이것이 실제로 작동합니까? 문서에 "이러한 옵션을 지정하면 어셈블러와 링커는 더 큰 객체와 실행 파일을 생성하며 속도도 느려집니다."라고 말합니다. 별도의 섹션을 갖는 것이 플래시 및 RAM 섹션이있는 마이크로에게는 의미가 있음을 이해합니다. 문서에있는이 설명이 마이크로 컨트롤러에 적용되지 않습니까?
Kevin Vermeer

내 경험은 AVR에서 잘 작동한다는 것입니다
Toby Jaffey

1
이것은 내가 사용한 대부분의 컴파일러에서 잘 작동하지 않습니다. register 키워드를 사용하는 것과 같습니다. 컴파일러에게 변수가 레지스터로 들어간다고 말할 수는 있지만, 좋은 옵티마이 저는 인간보다 훨씬 더 잘 수행 할 수 있습니다 (어떤 사람들이 생각 하듯이 실제로는 이것을 허용하지 않는 것으로 간주됩니다).
Kortuk

1
위치 할당을 시작하면 컴파일러가 특정 위치에 항목을 배치해야합니다. 고급 부트 로더 코드에는 매우 중요하지만 결정을 내릴 때 최적화 단계에서 처리해야하는 문제는 최적화 단계입니다. 할 수 있었다. 일부 컴파일러에서는 사용되는 코드에 대한 섹션을 갖도록 설계합니다. 이것은 컴파일러에게 사용을 이해하기 위해 더 많은 정보를 알려주는 경우에 도움이됩니다. 컴파일러가 제안하지 않으면하지 마십시오.
Kortuk

6

할당 된 스택 공간 및 힙 공간의 양을 검사 할 수 있습니다. 이들 중 하나 또는 둘 다가 초과 할당되면 상당한 양의 RAM을 다시 얻을 수 있습니다.

내 생각에는 동적 메모리 할당이 없을와 RAM의 2K에 맞는 시작하는 것을 프로젝트 (의 사용을위한 것입니다 malloc, calloc등). 이 경우 원래 작성자가 힙에 할당 된 일부 RAM을 남겨둔 것으로 가정하여 힙을 완전히 제거 할 수 있습니다.

찾기가 매우 어려운 버그가 발생할 수 있으므로 스택 크기를 줄이려면 매우주의해야합니다. 전체 스택 공간을 알려진 값 (초기 값으로 이미 발생하는 0x00 또는 0xff 이외의 값)으로 초기화하여 시작한 다음 시스템을 잠시 동안 실행하여 사용되지 않은 스택 공간을 확인하는 것이 도움이 될 수 있습니다.


이들은 매우 좋은 선택입니다. 임베디드 시스템에서 malloc을 사용해서는 안됩니다.
Kortuk

1
@Kortuk 이것은 임베디드의 정의와 수행되는 작업에 따라 다릅니다.
Toby Jaffey

1
@ 조비, 네, 이해합니다. 재시작이 0이고 Linux와 같은 OS가없는 시스템에서 Malloc은 매우 나쁠 수 있습니다.
Kortuk

malloc, calloc이 사용되는 동적 메모리 할당이 없습니다. 또한 힙 할당을 확인했으며 이미 0으로 설정되어 있으므로 힙 할당이 없습니다. 현재 할당 된 스택 크기는 254 바이트이고 인터럽트 스택 크기는 128 바이트입니다.
IntelliChick

5

코드에서 부동 소수점 수학을 사용합니까? 정수 수학 만 사용하여 알고리즘을 다시 구현하고 C 부동 소수점 라이브러리를 사용하는 오버 헤드를 제거 할 수 있습니다. 예를 들어 사인, 로그, exp와 같은 함수는 정수 다항식 근사치로 대체 될 수 있습니다.

코드에서 CRC 계산과 같은 알고리즘에 큰 조회 테이블을 사용합니까? 조회 테이블을 사용하는 대신 값을 즉시 계산하는 다른 버전의 알고리즘을 대체 할 수 있습니다. 작은 알고리즘은 속도가 느릴 수 있으므로 충분한 CPU주기를 확보해야합니다.

코드에 문자열 테이블, HTML 페이지 또는 픽셀 그래픽 (아이콘)과 같은 많은 양의 상수 데이터가 있습니까? 충분히 큰 경우 (예 : 10kB) 데이터를 축소하고 필요할 때 압축을 풀기 위해 매우 간단한 압축 체계를 구현할 가치가 있습니다.


2 개의 작은 룩업 테이블이 있으며, 둘 다 불행히도 10K가 아닙니다. 부동 소수점 수학도 사용되지 않습니다. :( 제안 해 주셔서 감사합니다. 좋습니다.
IntelliChick

2

더 컴팩트 한 스타일로 많은 코드를 재정렬하려고 할 수 있습니다. 코드가하는 일에 많이 의존합니다. 열쇠는 비슷한 것을 찾아서 다시 구현하는 것입니다. 극단적 인 경우는 Forth와 같은 고급 언어를 사용하는 것인데, C 나 어셈블러보다 코드 밀도를 높이기가 더 쉽습니다.

M16C를위한 Forth 는 다음과 같습니다 .


2

컴파일러의 최적화 수준을 설정하십시오. 많은 IDE에는 컴파일 타임 (또는 경우에 따라 처리 시간)을 희생하여 코드 크기 최적화를 허용하는 설정이 있습니다. 옵티 마이저를 두 번 다시 실행하고, 덜 일반적으로 최적화 할 수없는 패턴을 검색하고, 캐주얼 / 디버그 컴파일에 필요하지 않은 다른 모든 트릭을 찾아서 코드 압축을 수행 할 수 있습니다. 일반적으로 컴파일러는 기본적으로 중간 수준의 최적화로 설정됩니다. 설정을 살펴보면 정수 기반 최적화 스케일을 찾을 수 있어야합니다.


1
현재 최대 크기로 최적화되었습니다. :) 제안에 감사드립니다. :)
IntelliChick

2

이미 IAR과 같은 전문가 수준의 컴파일러를 사용하고 있다면 사소한 저수준 코드 왜곡으로 심각한 비용 절감을 위해 노력하고 있다고 생각합니다. 기능을 제거하거나 주요 기능을 수행하는 데 더 많은 노력이 필요합니다. 보다 효율적인 방식으로 부품을 다시 작성합니다. 원래 버전을 작성한 사람보다 똑똑한 코더가되어야합니다. RAM의 경우 현재 사용되는 방식을 매우 세밀하게 검토하고 동일한 RAM의 사용을 오버레이 할 수있는 범위가 있는지 확인해야합니다. 다른 시간에 다른 것들 (연합이 편리합니다). ARM / AVR에서 IAR의 기본 힙 및 스택 크기가 너무 컸기 때문에 가장 먼저 살펴볼 것입니다.


고마워 마이크. 코드는 이미 대부분의 장소에서 공용체를 사용하고 있지만 여전히 도움이 될 수있는 다른 장소를 살펴 보겠습니다. 또한 선택한 스택 크기를 살펴보고 이것이 최적화 될 수 있는지 확인합니다.
IntelliChick

적절한 스택 크기 크기를 어떻게 알 수 있습니까?
IntelliChick

2

확인할 사항-일부 아키텍처의 일부 컴파일러는 상수를 RAM에 복사합니다.-플래시 상수에 대한 액세스가 느리거나 어려운 경우 (예 : AVR) IAR의 AVR 컴파일러 는 상수를 RAM에 복사 하지 않기 위해 _ _flash qualifer가 필요합니다.


고마워 마이크. 그래도 이미 확인했습니다-M16C IAR C 컴파일러의 '쓰기 가능한 상수'옵션이라고합니다. ROM에서 RAM으로 상수를 복사합니다. 이 옵션은 내 프로젝트에서 선택 해제되어 있습니다. 그러나 정말로 유효한 수표! 감사.
IntelliChick

1

프로세서에 매개 변수 / 로컬 스택에 대한 하드웨어 지원이 없지만 컴파일러가 런타임 매개 변수 스택을 구현하려고 시도하고 코드를 다시 입력 할 필요가없는 경우 코드를 저장할 수 있습니다 자동 변수를 정적으로 할당함으로써 공간. 경우에 따라 수동으로 수행해야합니다. 다른 경우에는 컴파일러 지시문이이를 수행 할 수 있습니다. 효율적인 수동 할당은 루틴간에 변수를 공유해야합니다. 루틴이 다른 범위에서 "범위 내"로 간주하는 변수를 사용하지 않도록하기 위해 이러한 공유를 신중하게 수행해야하지만 경우에 따라 코드 크기 이점이 중요 할 수 있습니다.

일부 프로세서에는 일부 매개 변수 전달 스타일을 다른 것보다 효율적으로 만들 수있는 호출 규칙이 있습니다. 예를 들어, PIC18 컨트롤러에서 루틴이 단일 1 바이트 매개 변수를 사용하는 경우 레지스터에 전달 될 수 있습니다. 그 이상이 필요한 경우 모든 매개 변수를 RAM으로 전달해야합니다. 루틴이 2 바이트의 1 바이트 매개 변수를 사용하는 경우 전역 변수에서 하나를 "전달"한 후 다른 하나를 매개 변수로 전달하는 것이 가장 효율적일 수 있습니다. 널리 사용되는 루틴을 통해 비용을 절감 할 수 있습니다. 전역을 통해 전달 된 매개 변수가 단일 비트 플래그이거나 일반적으로 0 또는 255의 값을 갖는 경우 (특히 RAM에 0 또는 255를 저장하기위한 특수 명령이 있기 때문에) 특히 중요 할 수 있습니다.

ARM에서 자주 사용되는 전역 변수를 구조에 배치하면 코드 크기가 크게 줄어들고 성능이 향상 될 수 있습니다. A, B, C, D 및 E가 별도의 전역 변수 인 경우 모든 변수를 사용하는 코드는 각 주소를 레지스터에로드해야합니다. 레지스터가 충분하지 않으면 해당 주소를 여러 번 다시로드해야 할 수도 있습니다. 반대로, 동일한 전역 구조 MyStuff의 일부인 경우 MyStuff.A, MyStuff.B 등을 사용하는 코드는 MyStuff의 주소를 한 번만로드 할 수 있습니다. 큰 승리.


1

1. 코드가 많은 구조에 의존하는 경우 구조 멤버가 가장 많은 메모리를 차지하는 멤버에서 가장 적은 순서로 정렬되도록하십시오.

예 : "uint16_t uint8_t uint32_t"대신 "uint32_t uint16_t uint8_t"

이것은 최소한의 구조 패딩을 보장합니다.

해당하는 경우 변수에 const를 사용하십시오. 이렇게하면 해당 변수가 ROM에 있고 RAM을 먹지 않게됩니다.


1

고객 코드를 압축하는 데 성공적으로 사용한 몇 가지 (아마도 명백한) 트릭 :

  1. 비트 필드 또는 비트 마스크로 플래그를 압축합니다. 이것은 일반적으로 불리언이 정수로 저장되어 메모리를 낭비하기 때문에 유리할 수 있습니다. 이렇게하면 RAM과 ROM이 모두 저장되며 일반적으로 컴파일러에서 수행하지 않습니다.

  2. 코드에서 중복성을 찾고 반복문을 실행하기 위해 루프 나 함수를 사용하십시오.

  3. 또한 if(x==enum_entry) <assignment>열거 형 항목을 배열 인덱스로 사용할 수 있도록주의하여 상수의 많은 명령문을 인덱스 배열 로 대체하여 ROM을 저장했습니다.


0

가능하면 작은 함수 대신 인라인 함수 나 컴파일러 매크로를 사용하십시오. 인수를 전달할 때 크기와 속도 오버 헤드가 있으며 함수를 인라인으로 만들면 해결할 수 있습니다.


1
괜찮은 컴파일러는 한 번만 호출되는 기능에 대해 자동 으로이 작업을 수행해야합니다.
mikeselectricstuff

5
인라인은 일반적으로 속도 최적화에 더 유용하며 일반적으로 크기가 커집니다.
Craig McQueen

일반적으로, 코드 크기를 증가 같은 사소한 기능 제외됩니다 인라인int get_a(struct x) {return x.a;}
드미트리 Grigoryev

0

지역 변수를 동일한 크기의 CPU 레지스터로 변경하십시오.

CPU가 32 비트 인 경우 최대 값이 255를 초과하지 않더라도 32 비트 변수를 사용하십시오. 8 비트 변수를 사용한 경우 컴파일러가 상위 24 비트를 마스킹하는 코드를 추가합니다.

가장 먼저 살펴볼 것은 for-loop 변수입니다.

for( i = 0; i < 100; i++ )

이것은 8 비트 변수를위한 좋은 장소처럼 보일 수 있지만 32 비트 변수는 더 적은 코드를 생성 할 수 있습니다.


코드가 저장 될 수 있지만 RAM이 소모됩니다.
mikeselectricstuff

해당 함수 호출이 호출 추적의 가장 긴 분기에있는 경우에만 RAM을 사용합니다. 그렇지 않으면 다른 기능에 이미 필요한 스택 공간을 재사용하고 있습니다.
Robert

2
일반적으로 지역 변수를 제공하면 true입니다. RAM이 부족하면 전역 변수, 특히 배열의 크기가 저축을 찾기에 좋은 장소입니다.
mikeselectricstuff

1
흥미롭게도 또 다른 가능성은 부호없는 변수를 부호있는 것으로 바꾸는 것입니다. 컴파일러가 부호없는 short를 32 비트 레지스터로 최적화하는 경우 값을 65535에서 0으로 줄 이도록 코드를 추가해야합니다. 그러나 컴파일러가 부호있는 단락을 레지스터에 최적화하면 해당 코드가 필요하지 않습니다. 32767 이상으로 short가 증가하면 어떤 일이 일어날 지 보장하지 않기 때문에 컴파일러는이를 처리하기 위해 코드를 생성 할 필요가 없습니다. 내가 본 적어도 두 개의 ARM 컴파일러에서 부호있는 짧은 코드는 그 이유로 부호없는 짧은 코드보다 작을 수 있습니다.
supercat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.