컴파일러 자체 최적화, 산술연산, 루프, if/switch, 함수 관련 최적화를 순서대로 보겠다.
최적화는 보통 프로파일러와 함께쓰이며 여러 최적화 기법을 사용하며 그 결과를 비교해볼 필요가 있다.
컴파일러 성능 최적화
O1,O2,O3 최적화
gcc 컴파일러의 최적화 옵션이다.
-O1, -O2, -O3의 옵션을 통해 선택할 수 있다.
O1 (최적화 레벨 1):
- 기본 최적화: 가장 기본적인 최적화 레벨로, 실행 속도를 크게 저해하지 않으면서 코드 크기를 줄이고 성능을 약간 개선하는 데 중점을 둔다.
O2 (최적화 레벨 2):
- 중간 수준 최적화: 더 공격적인 최적화를 통해 코드 실행 속도를 현저하게 향상시킨다. 디버깅 가능성을 약간 희생하고도 성능 최적화를 더 깊이 적용한다.
O3 (최적화 레벨 3):
- 고급 최적화: 가장 높은 수준의 최적화로, 가능한 모든 최적화 기법을 적용하여 코드 실행 성능을 최대한 높인다. 그러나 이로 인해 코드 크기가 크게 증가할 수 있으며, 디버깅이 매우 어려워질 수 있다.
산술연산
1. 부동소수점 피하기
부동 소수점 (float, double) 은 되도록 사용 하지 말자
부동 소수점 수는 그 구조가 매우 복잡하고 , 연산도 복잡하므로 부동 소수점 수를 가지고 하는 연산 자체도 매우 느림
피하는 방법에는 고정 소수점을 이용하는 것으로, 단순히 소수점 둘째 자리나 첫째 자리 정도의 정밀도를 요구한다면 단순히 그 수에 x 10, x 100 을 해서 정수 자료형으로 다룰 수 있다.
#include <stdio.h>
int main() {
// 부동 소수점 대신 정수를 사용한 고정 소수점 연산
int a = 1234; // 12.34를 100배로 변환한 값
int b = 5678; // 56.78을 100배로 변환한 값
int sum = a + b; // 고정 소수점에서의 덧셈
printf("Sum: %d.%02d\n", sum / 100, sum % 100); // 결과를 다시 소수점 형식으로 출력
return 0;
}
위는 예시 코드이다.
2. 나눗셈 피하기
정수 나눗셈 연산(DIV) 의 경우 10 cycle 정도 필요하고, 덧셈의 경우 1 cycle 에 끝난다.
즉 나눗셈은 덧셈에 비해 10배는 느리다.
위와 같이 나눗셈 연산을 피하거나 Shift 연산을 통해 대체하여 나눗셈을 피한다.
3. 비트 연산 활용하기
비트 연산(OR, AND, XOR 등) 은 컴퓨터에서 가장 빠르게 실행되는 연산이므로, 사용할 수 있는 상황에서는 최대한 활용하는게 좋다.
루프
1. 끝낼 수 있을때 끝내기
어찌보면 당연할 수도 있는데... 찾는 등에 종료 조건을 만들 수 있는 루프라면 break문을 돌게 하여 루프를 종료시킬 수 있도록한다.
2. Loop Unrolling
한번 돌때 많이 돌기: 한번 돌때마다 종료조건을 확인하고, 비교하는 등의 조건 확인을 하므로 한번 돌때 최대한 많은 작업을 수행하는 게 좋다.
안쓸 수 있으면 안쓰는 게 좋다.
3. 0과 비교하기
0과 비교하는 명령어는 CPU에서 따로 만들어져 있어, 다른 수와의 비교문보다 빠르게 작동한다. 따라서 아래처럼 for 문을 짜면 더 빠른 작동을 할 수 있다.
if 및 Switch 문
1. 순차적 비교에서는 Switch문 사용
순차적인 정수 값을 비교하는 경우에는 switch 문 을 사용하는 것이 매우 요긴하다.
if문의 경우 모든 수를 비교해봐야하는 반면, switch는 해당 값의 case로 jump하기 때문이다.
따라서 비교 연산을 최소화할 수 있다.
2. binary break down
위에서의 순차적인 정수 비교를 위와같이 참 거짓으로 계속해서 이분하여 찾는 조건문으로 만들 수 있다.
평균 조건 비교 횟수는 위의 순차적 비교는 4, binary break down을 적용한 함수는 3번이다.
함수
1. 함수 인라인화
함수 인라인화(Function Inlining)는 컴파일러가 함수 호출을 최적화하는 기법 중 하나로, 함수 호출을 실제 함수로 점프하는 대신 호출된 함수의 코드를 호출한 위치에 직접 삽입하는 방법이다. 이 과정을 통해 함수 호출에 따른 오버헤드를 줄이고, 실행 성능을 향상시킬 수 있다.
int add(int a, int b) {
return a + b;
}
int main() {
int result = add(3, 4);
return result;
}
위 코드를 아래로 줄이며, 함수호출 오버헤드를 줄인다고 보면된다.
int main() {
int result = 3 + 4;
return result;
}
2. 인자 포인터화
- Arm 컴파일러의 경우 파라메터 4개까지는 register 사용한다.
- 4개가 넘게 되면 stack에 복사하여 함수에 전달한다.
'CPU' 카테고리의 다른 글
[CPU] ARM 프로세서 (1) | 2024.08.21 |
---|