[IAR시스템즈=라파엘 토빈거(Rafael Taubinger)] 임베디드 엔지니어는 프로젝트에서 코드 최적화를 가능한 최고 수준으로 달성하기 위해 노력하곤 한다. 코드의 최적화란 가장 빠른 실행 코드를 생성할 수 있다는 것일 수 있으며, 가능한 한 많은 코드나 기능을 장치에 맞추는 작업이 포함될 수 있다.

컴파일러가 RISC-V 기반 어플리케이션에서 최적화를 달성하기 위해 코드로 무엇을 할 것인지에 고민이 필요하다. 보다 개선된 결정을 내리기 위해 개발자가 수행할 수 있는 작업은 살펴볼 수 있다.

[사진=게티이미지뱅크]
[사진=게티이미지뱅크]

◆ 사용가능한 핵심 기능은 무엇인가

RV32I에서 ▲M(정수 곱셈, Integer Multiplication) ▲A(원자 명령어, Atomic Instructions) ▲F(단일 부동 소수점, single floating point) ▲D(이중 부동 소수점, double floating point) ▲C (압축된 명령어, compressed instructions) ▲B (비트 조작, bit manipulations) 등 표준 확장을 얻을 수 있는 기본 명령어 세트를 확인할 수 있다.

표준 확장자. 위키피디아(Wikipedia) [이미지=IAR시스템즈]

대부분의 확장자는 비준 또는 동결됐지만 현재 작업 중인 새 확장자가 있다. 다양한 코어에서 지원되는 확장자의 예시는 출력 화면에서 볼 수 있다.

표준 확장자 지원의 예시. [이미지=IAR시스템즈]

일반 디바이스 RV32를 사용하면, M(정수 곱셈), F(단일 부동 소수점) D(이중 부동 소수점) 및 C(압축된 명령어)를 지원한다는 것을 알 수 있다. C(압축된 명령어)는 작업을 위한 짧은 16비트 명령어를 추가해 정적 및 동적 코드 크기를 줄임으로써, 코드의 크기가 평균 25%–30% 감소해 전력 소비 및 메모리 사용 절감에 기여한다.

RV32E 기본 명령어 (임베디드) 세트는 16개 레지스터가 있는 임베디드 마이크로컨트롤러를 위해 훨씬 더 작은 기본 코어를 제공하도록 설계돼 있다.

설계자는 머신러닝, 저전력 어플리케이션 또는 계기 및 모터 제어를 위한 최적화된 SoC와 같은 특정한 요구 사항을 위해 확장자를 자유롭게 구현할 수 있다. 표준 확장자 또는 사용자 정의 확장자의 목적은, 대체로 하나 또는 몇 개의 주기가 필요한 하드웨어에서 수행되는 계산과 처리에서 더욱 빠르게 응답하도록 하는 것이다.

◆ RISC-V을 위한 전문적인 툴이 필요한 이유는 무엇인가

RISC-V의 성장과 함께 핵심 기능과 확장을 최대한 활용할 수 있는 전문적인 도구가 필요한다. 잘 설계되고 최적화된 SoC는, 가장 최적화된 코드를 실행해야만 기업의 혁신을 촉진하고 높은 수준의 제품을 제작할 수 있고, 비용 상의 이점을 극대화할 수 있다.

코드 밀도와 관련해 저장 가능한 모든 바이트가 중요하다. 전문화된 툴은 필요한 요구 사항에 적합도를 극대화하는 어플리케이션 최적화에 기여한다. 어플리케이션 최적화를 통해, 고객은 메모리가 작은 장치를 사용해 비용을 절감하거나 기존 플랫폼에 기능을 추가해 가치를 집계할 수 있다.

다양한 변환을 통한 컴파일러 최적화. [이미지=IAR시스템즈]

RISC-V용 전문 컴파일러는 다른 툴에 비해, 평균적으로 7-10% 가량 더 작은 코드를 생성할 수 있다.

◆ 더욱 높은 수준의 최적화를 위한, 컴파일러에 적용하기 좋은 코드의 작성

최적화된 컴파일러는 코드 실행에 가장 적합한 순서로 올바른 명령어를 선택해, 작고 빠른 코드를 생성하려고 한다. 이는 소스 프로그램에 여러가지 형태의 변형을 반복적으로 적용하는 실행이다. 최적화는 대부분 건전한 이론적 기초에 기반한 수학적 또는 논리적 규칙을 따릅니다. 다른 형태로의 변환은 일부 변환에서 종종 좋은 코드를 생성하거나 추가적인 최적화의 기회를 발생시킨다는 점을 경험적으로 발견할 수 있는 휴리스틱 방법론에 기초한다.

소스 코드를 작성하는 방식에 따라, 프로그램에 대한 최적화 적용 여부를 결정할 수 있다. 소스 코드를 약간 변경함으로써 컴파일러가 생성할 수 있는 코드의 효율성에 상당한 영향을 미칠 수 있는 경우가 이따금씩 발생한다.

?:-표현식, 후위 증분 및 쉼표 표현식을 사용해, 가능한 한 적은 수의 라인으로 코드를 작성해 단일 표현식에서 많은 사이드 이펙트를 줄인다고 컴파일러가 더 효율적인 코드를 생성하게 되는 것은 아니다. 가독성이 높은 스타일로 코드를 작성하는 것이 가장 좋은 힌트가 된다.

개발자는 소스 코드의 다음 힌트에 주의를 기울여, 컴파일러의 결정이 개선되도록 기여할 수 있다.

함수 콜은 단 한 번만 수행한다.

컴파일러가 하위 표현식이 필요한 경우일지라도 하위 표현식에는 컴파일러가 선험적으로 알지 못하는 사이드 이펙트가 있을 수 있기 때문에 컴파일러의 공통된 하위 표현식 탐색 작업은 어려운 것이 일반적이다. 따라서 컴파일러는 공통된 하위 표현식을 탐색하라는 지시가 있을 때, 동일한 함수를 여러 번 호출해 코드 공간 낭비와 실행 오버헤드를 발생시킨다. 함수를 변수에 할당하고 (레지스터에 저장될 가능성이 높으며) 쉽게 액세스할 수 있는 레지스터에 있는 동안 작업을 수행하는 것이 좋다.

[이미지=IAR시스템즈]
[이미지=IAR시스템즈]

복사가 아닌 레퍼런스로 전달해야 한다.

기본 타입의 변수 그 자체를 전달하는 것이 아니라, 해당 기본형 변수에 대한 포인터를 전달하면, 해당 기본형을 RAM이나 레지스터의 어딘가에 복사하면 컴파일러 오버헤드를 줄일 수 있고 큰 배열의 경우 실행 시간을 상당히 절약할 수 있다. 기본형 변수를 복사해 전달할 경우에는 컴파일러가 해당 기본형 변수의 내용을 복사하는 코드를 삽입해야 한다.

데이터 크기를 정확한 크기로 사용해야 한다.

8051 또는 AVR과 같은 일부 MCU는 8비트 마이크로프로세서이고 일부 MSP430와 같은 모듈들은 16비트이며 Arm 및 RISC-V 계열의 모듈은 32비트이다. 코어에 ‘부적합한’ 크기를 사용하는 경우, 컴파일러는 그 안에 포함된 데이터를 해석하기 위해 추가 오버헤드가 발생한다.

32비트 MCU는 작업 수행을 위한 필요한 값을 획득하기 위해서는 시프트, 마스크 및 부호 확장 작업을 수행해야 한다. 따라서 I/O 수행 없이 정확한 수의 비트 또는 더 큰 유형(예: 문자 배열)이 필요없는 한, 데이터 유형에 대해 MCU의 고유 크기로 사용하는 것이 가장 좋다. 그렇지 않을 경우, 너무 많은 메모리를 사용하게 된다.

적합한 부호를 사용해야 한다.

변수의 부호는 컴파일러에서 생성되는 코드에 영향을 줄 수 있다. 예를 들어, (C 언어의 규칙에 따라) 음수로 나누는 것은 양수로 나누는 것과는 다르게 취급됩니다. 따라서 어플리케이션에서 절대 음수가 아닌 부호 있는 숫자를 사용하면, 코드 공간과 실행 시간을 모두 낭비하는 추가 테스트 및 점프 조건이 코드에 발생할 수 있다. 또한, 변수의 목적이 비트 조작 수행이라면 무부호 변수여야 한다. 그렇지 않을 경우, 이동과 마스킹 시 의도치 못했던 결과가 나타날 수 있다.

묵시적 형변환을 피해야 한다.

C는 묵시적 형 변환 (예: float와 integer 사이, int와 long long 사이)을 수행하는 경우가 많으나, 이는 자유롭게 진행되는 작업이 아니다. 더 작은 유형에서 더 큰 유형으로 형 변환할 경우에는 부호 확장 작업이 필요하며, float로 또는 float에서 형 변환할 때에는 부동 소수점 라이브러리가 필요하다(이 경우, 코드 크기가 크게 증가할 수 있음). 추가적인 오버헤드를 회피하기 위해서는 묵시적 형 변환은 최대한 피해야 한다. 이러한 문제는 int와 함수 포인터를 상호 교환해 사용하는 것에 익숙한 데스크탑 프로그래머가 임베디드 프로그래밍의 영역으로 점프할 때 쉽게 발견된다.

[이미지=IAR시스템즈]
[이미지=IAR시스템즈]

함수 프로토타입을 사용해야 한다.

프로토타입이 없을 경우에는 C 언어의 규칙에 따라 모든 인수가 정수로 승격되어야 하는데 이전에 논의한 바와 같이 런타임 라이브러리에서 불필요한 오버헤드로 귀결될 수 있다.

전역 변수를 임시 변수로 읽어야 한다.

함수 내에서 전역 변수에 여러 번 액세스하는 경우 로컬 임시 변수로 읽어 들일 수 있다. 그렇지 않으면 이 변수에 액세스할 때마다 메모리에서 읽어야 한다. 전역 변수를 로컬 임시 변수로 입력하면, 컴파일러는 레지스터에 값을 할당하기 때문에 연산이 더욱 효율적으로 수행될 수 있다.

[이미지=IAR시스템즈]

인라인 어셈블리를 자제해야 한다

인라인 어셈블리를 사용하면 프로그램의 최적화에 매우 지장을 준다. 인라인 어셈블리를 사용할 경우, 최적화된 프로그램은 코드 블록에 대해 무지한 상태가 되기 때문에 최적화가 불가능하다. 코드가 수행하는 작업에 대해서도 알 수 없기 때문에 작성한 블록의 명령 스케줄링도 불가능하다(특히 DSP에 손상을 줄 수 있음). 또한 개발자는 매번 작성한 코드를 검사해 의도치 않은 사이드 이펙트의 발생을 방지하기 위해 최적화된 C코드에 배치 상황이 올바른 지도 확인해야 한다. 인라인 어셈블러의 이식성은 매우 열악하므로, 새 아키텍처로 이동하기로 결정한 경우 재작성(그리고 해당 재작성 결과에 대한 이해)이 필수다. 굳이 어셈블러를 인라인해야 할 경우, 자체 어셈블러 파일로 분할하고 소스와는 분리된 상태를 유지해야 한다.

코드를 너무 깔끔하게 작성하지 말아야 한다

소스 라인을 더 적게 작성하고 C 스트럭처를 깔끔하게 사용하면, 코드가 더 작아지거나 코드 수행 속도가 더 빨라질 것이라는 잘못된 믿음을 가진 개발자들이 있다(이러한 경우에 컴파일러의 작업이 늦게 도출됨).

이러한 코딩의 경우 결과의 가독성도 저하되며, 원래 작성한 사람 외에는 이해할 수 없는 컴파일하기도 어려운 코드이다. 명확하고 직접적인 방식으로 작성하면, 코드의 가독성이 향상되고 컴파일러가 정보에 근거해 코드 최적화의 최선의 방법을 결정하도록 도와줄 수 있다.

일례로 다른 변수에 최하위 21비트가 설정된 경우, 변수 b의 최하위 비트를 설정한다고 가정하면 너무 깔끔한 코드는 인수가 0이 아닌 경우(C의 "true"는 0을 제외한 모든 값임) 0을 반환하고 인수가 0이면 1을 반환하는 C의 ! 연산자를 사용한다.

반면에, 직접적인 솔루션은 일련의 비트 설정에 따라 조건적으로 컴파일하기 쉽게 되는데, 이는 비트 설정을 통한 작업은 명백하고 마스킹이 시프트보다 더 효율적일 가능성이 높기 때문이다. 어떠한 상황이든 간에, 깔끔한 코딩과 직접적인 코딩, 두 솔루션 모두 동일한 코드를 생성해야 할 것이다.

너무 깔끔한 코드의 경우에는 각각의 연산을 조건에 따라 컴파일시키는 두 가지 ! 작업을 모두 수행하기 때문에, 결과적으로 더 많은 코드를 생성할 수 있다.

[그림=IAR시스템즈]

또 다른 예는 조건부 값을 사용한 계산과 관련된다. ‘Clever’ 코드에서는, 생성된 코드가 직접적인 코딩에서와 동일한 테스트를 포함하고, str에 추가할 1 또는 0을 유지하는 임시 변수를 추가하기 때문에 기계 코드가 더욱 크게 생성된다. 직접적이고 간단한 코드는, 전체 추가가 아닌 단순 증분을 사용할 수 있으며 중간 결과 생성이 필요하지 않다.

[이미지=IAR시스템즈]

순서대로 스트럭처에 접근해야 한다

스트럭처에서 점프하는 대신 스트럭처의 한 요소에서 다음 요소로 이동하는 방식으로 정렬하면, 컴파일러는 스트럭처포인터로부터의 오프셋을 계산하려고 하는 대신, 증분 연산을 활용해 스트럭처의 그 다음 요소로 액세스할 수 있다. 정적으로 할당된 스트럭처에서는 주소가 선험적으로 계산되기 때문에 이 작업을 수행하는 코드를 저장하지 않지만, 대부분의 어플리케이션에서는 동적으로 수행된다.

임베디드 컴파일러의 여러 기능 중, 특히 최적화 기능은 지난 30년 동안 크게 발전돼 왔다. 최신의 컴파일러는 매우 엄격하고 효율적인 코드 생성을 위해 다양한 테크닉들을 사용하기에 명확하고 논리적이며, 소스 작성의 간결화에 집중할 수 있다. 모든 개발자는 소프트웨어에서 최적의 효율성을 달성하기 위해 전력을 다한다. 컴파일러는 이미 엄청난 수준의 최적화가 가능한 놀랍도록 복잡한 소프트웨어이지만, 간단한 힌트들을 이용하시면 훨씬 더 높은 수준의 효율성을 달성하는 데 도움이 된다.

회원가입 후 이용바랍니다.
개의 댓글
0 / 400
댓글 정렬
BEST댓글
BEST 댓글 답글과 추천수를 합산하여 자동으로 노출됩니다.
댓글삭제
삭제한 댓글은 다시 복구할 수 없습니다.
그래도 삭제하시겠습니까?
댓글수정
댓글 수정은 작성 후 1분내에만 가능합니다.
/ 400
내 댓글 모음
저작권자 © 테크월드뉴스 무단전재 및 재배포 금지