[CS] 컴파일러의 최적화
[CS] 컴파일러의 최적화
컴파일러 최적화의 원리와 주요 영역
컴파일러는 소스 코드를 기계어로 변환하면서 성능을 향상시키기 위해 다양한 최적화 기법을 적용합니다.
컴파일러가 수행하는 최적화는 크게 코드 크기 감소, 실행 속도 향상, 메모리 사용 최적화로 나눌 수 있습니다.
아래는 일반적으로 컴파일러가 수행하는 주요 최적화 영역과 그 특징입니다.
컴파일러 최적화의 기본 개념
1. 컴파일러 최적화의 목표
- 프로그램을 더 빠르게 실행시키거나 메모리 사용량을 줄이는 것입니다.
- 최적화 수준에 따라 (
-O1,-O2,-O3,-Ofast) 최적화 강도와 범위가 다릅니다.
2. 최적화의 유형
- 고수준 최적화(High-Level Optimization): 소스 코드 수준에서 최적화
- 저수준 최적화(Low-Level Optimization): 어셈블리 코드 수준에서 최적화
- 플랫폼 최적화(Platform-Specific Optimization): 특정 아키텍처에 맞춘 최적화
주요 컴파일러 최적화 영역
1. 인라인 확장 (Inlining)
- 함수 호출 비용을 줄이기 위해 작은 함수의 코드 자체를 호출 위치에 삽입합니다.
- 함수 호출 오버헤드 제거
- C++:
inline키워드 또는 컴파일러 자동 인라인
예제:
1
2
inline int add(int a, int b) { return a + b; }
int result = add(3, 5); // 함수 호출이 아닌, 직접 연산으로 대체
2. 상수 폴딩 (Constant Folding)
- 컴파일 시간에 계산이 가능한 상수 연산을 미리 수행하여 결과값으로 대체합니다.
예제:
1
int x = 2 * 3; // 컴파일 시 x = 6으로 변경
3. 상수 전파 (Constant Propagation)
- 변수에 할당된 상수를 해당 변수 사용 위치로 전파하여 중간 변수를 제거합니다.
예제:
1
2
int x = 5;
int y = x + 3; // -> int y = 8;
4. 루프 언롤링 (Loop Unrolling)
- 반복문을 펼쳐서(loop unrolling) 반복 횟수를 줄입니다.
- 루프 제어 오버헤드 감소
예제:
1
2
3
4
5
6
for (int i = 0; i < 4; i++) arr[i] = i;
// -> 펼쳐진 형태
arr[0] = 0;
arr[1] = 1;
arr[2] = 2;
arr[3] = 3;
5. 루프 불변 코드 제거 (Loop-Invariant Code Motion)
- 루프 내에서 반복 계산이 필요 없는 코드를 루프 밖으로 이동시킵니다.
예제:
1
2
3
4
5
6
7
8
9
for (int i = 0; i < n; i++) {
int temp = x * y; // 반복 계산
arr[i] = temp + i;
}
// -> 개선
int temp = x * y;
for (int i = 0; i < n; i++) {
arr[i] = temp + i;
}
6. 공통 부분식 제거 (Common Subexpression Elimination)
- 반복해서 등장하는 동일 연산을 한 번만 계산하도록 변경합니다.
예제:
1
2
3
4
int x = (a * b) + (a * b); // 중복 계산
// -> 개선
int temp = a * b;
int x = temp + temp;
7. 죽은 코드 제거 (Dead Code Elimination)
- 사용되지 않는 변수나 불필요한 코드 블록을 제거하여 코드 크기를 줄입니다.
예제:
1
2
int x = 5;
x = 10; // x = 5는 의미 없음 -> 제거
8. 강제 인라인 (Forced Inline)
- 특정 조건에서 반드시 인라인으로 처리되도록 컴파일러 지시자를 사용합니다.
- MSVC:
__forceinline, GCC:__attribute__((always_inline))
9. 레지스터 할당 (Register Allocation)
- 빈번히 사용되는 변수를 레지스터에 저장하여 메모리 접근을 줄입니다.
10. 명령어 병렬화 (Instruction Scheduling)
- 프로세서 파이프라인을 최적화하여 명령어를 병렬로 실행하도록 재배치합니다.
11. 루프 벡터화 (Loop Vectorization)
- 루프를 벡터화하여 SIMD 명령어로 병렬 연산을 수행합니다.
- 컴파일러가 자동으로 벡터화할 수 있으나, 명시적으로
#pragma omp simd등을 사용하여 강제할 수도 있습니다.
12. 메모리 정렬 (Memory Alignment)
- 데이터 구조를 메모리 경계에 맞게 정렬하여 메모리 접근 속도를 높입니다.
결론
컴파일러 최적화는 성능을 개선하기 위해 다양한 코드 변환 기법을 적용합니다.
코드 작성 시 최적화를 고려하기보다는, 알고리즘 개선이 더 중요합니다.
그러나, 최적화가 중요한 성능 병목에서는 컴파일러 옵션을 활용하여 적절한 수준의 최적화를 적용하는 것이 좋습니다.
최적화 결과를 직접 확인하려면 어셈블리 코드 분석이나 프로파일링 도구를 사용하여 변화를 분석할 수 있습니다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.