배너
닫기

테크노트

배너

[기고] 코드 최적화

  • 등록 2019.06.27 15:10:08
URL복사

[첨단 헬로티]



SW 개발에 있어 코드 최적화는 중요한 기능이다. 동일하게 작성된 소스코드를 다양한 기법들을 활용해 코드 수행 속도 또는 코드 사이즈에 목적을 두고 최적화가 가능하다. 동일한 성능의 하드웨어에서 동작하더라도 코드의 수행속도를 최대한으로 높일 수도 있으며, 동일 동작을 하는 코드의 메모리 사용량을 최소화 할 수 있다.

특히, 메모리 사용량의 감소는 동일 동작 구현을 위해 작은 용량의 메모리사용이 가능해져 비용을 절감하실 수도 있다. 코드 수행 속도의 향상은 애플리케이션의 동작 응답성을 높여서 다양한 기능 구현을 가능하게 할 수 있다. 이처럼 코드 최적화 기능으로 개발에 많은 이득을 얻을 수 있다.


최적화 범위 

최적화 옵션을 지정하는 범주는 전체 애플리케이션 또는 개개의 C 파일 단위로 가능하다. 또한, 개별적인 함수에 대해 적용되는 최적화를 배제할 수도 있다. 그리고 프로젝트 옵션이 아닌 #pragma optimize 지시어를 사용해 최적화 단계 및 형식을 함수 단위 별로 지정할 수 있다(그림1).


최적화 단계 

최적화는 총 4단계로 이루어져 있으며, None, Low, Medium, 그리고 High 단계로 High단계의 경우 Balanced, Size, Speed에 대한 설정이 가능하다. 세부적인 변환 방식에 대한 선택을 할 수 있다. 


최적화를 위한 코드 변형 

컴파일러의 최적화 기능을 구현하기 위해 작성된 코드에 대해 여러 방식으로 코드를 변형(transformations)해 보다 적고 빠른 코드를 생성하게 된다. 컴파일러는 소스 코드의 로직(logic)을 분석해 동일한 결과를 구현할 수 있도록 보다 효과적인 실행 코드를 생성한다. 이러한 경우 개발자가 작성한 프로그램 로직과 다른 방식으로 실행되므로 디버깅 시 어려울 수 있는 점을 주의 해야 한다.


코드 최적화를 위한 다양한 변환 기법

1. Common subexpression elimination

공통된 부분의 코드를 중복해서 실행하지 않고 한번 실행한 값을 이용하여 최적화하는 방법이다. 그림 예제에서 기술된 것처럼 b * C의 공통부분을 한번만 실행하여 처리하게 됩니다(그림 2).



2. Loop Unrolling

대체로 많은 시간이 소요되는 부분은 루프(loop)를 돌면서 실행되는 코드를 생각해 볼 수 있습니다. 다중 루프의 경우 많은 시간이 소요될 수 밖에 없다. 범위에 도달했는지를 검사하기 위해 값을 비교하고 증가하는 등의 작업이 진행된다. 이러한 작업들을 줄일 수 있다면 실행 속도를 보다 빠르게 생성할 수 있다(그림 3).



예제의 경우는 루프의 for문에 대한 코드를 삭제하고 만든 코드이다. 반복 횟수가 많은 경우 에는 이러한 방식으로 구현될 수 없으며, 반복되는 루프의 횟수를 줄일 수 있으면 그 만큼 실행 속도가 빨라지게 된다.



3. Function inlining

함수를 호출하면 복귀할 위치를 스택에 저장하고 해당 함수로 점프한다. 해당 함수에서는 일반적으로 사용하는 레지스터를 스택에 보관(push)하고 함수의 실행을 마치면 레지스터 값을 복원(pop)하고 보관해 둔 주소로 점프하는 과정을 거치게 된다. 

이러한 시간을 줄이기 위해 작은 코드의 함수를 직접 삽입해 사용한다. 그림 4의 예제처럼 함수를 호출하지 않고 코드를 삽입한 상태로 변형한다.



4. Code motion

단어에서 의미 하듯이 코드를 이동해 보다 효율적인 실행 코드를 생성합한다. 그림 5 예제의 경우 x * x의 부분은 루프를 돌면서 시간 소요가 계속적으로 발생되지만 값의 변화는 없다. 이러한 경우처럼 해당 코드를 이동하여 최적화된 코드를 생성하는 방법이다.



5. Type-based alias analysis

데이터 형(type)이 다르면 별도의 선언이 없는 경우 서로 다른 위치에 데이터가 존재하게 된다.

예를 들어 char a; 와 int b; 의 경우, 변수 a와 b는 서로 다른 위치에 있게 된다.

그림 6의 예제의 경우는 short *p1과 long *p2의 변수는 서로 독립적으로 인식된다. *p2값이 0이고 변함이 없으므로 return *p2의 실행 코드를 return 0로 변경한다. 이런 경우처럼 컴파일러가 인식하고 있는 상황과 다르게 임의로 포인터를 설정해 처리하는 경우 동작 오류를 발생할 수 있으니 주의해야 한다.



6. Static clustering

static 또는 global 변수에 있어 사용하는 함수에서 보다 효율적인 처리를 위해 해당 변수들의 위치를 변경하게 됩니다. 하나의 기준 어드레스에서 오프셋으로 변수들을 엑세스할 수 있도록 재배치한다(그림 7).



7. Instruction scheduling

여러 개의 파이프라인(pipeline)을 사용하는 경우 앞의 파이프라인에서 결과가 나오지 못하면 뒤의 파이프라인이 기다리는 상태가 된다. 이를 stall이라고 말하며, 이러한 stall 현상을 최소화하도록 명령어를 재배열하는 방법을 말한다. 참고로 Cortex-M3 코어의 경우 3개의 파이프라인으로 구성되어 있다(그림 8).



맺음말

코드 최적화의 기능은 메모리 사용량의 절감과 최상의 퍼코먼스를 만들 수 있는 기능이다. 하지만, 코드가 컴파일러의 분석에 의해 자동 분석되고 변형되므로 개발된 코드의 동작의도와 다르게 변형될 수 있으므로 동작 테스트가 병해오길 권장한다. 코드 변형에 대한 숙지와 명확한 코드의 작성과 함께 최적화 기능을 활용하여 최상의 퍼포먼스와 최적의 사이즈를 갖는 실행 코드를 만들어라.


글 / IAR Systems 이현도 과장




















주요파트너/추천기업