Q: C++ 컴파일러가 사용하는 변수에 대한 최적화 기법들의 종류
컴파일러는 실행 속도를 높이고 메모리 사용량을 줄이기 위해 다양한 최적화를 수행합니다. **변수**와 관련된 대표적인 기법은 다음과 같습니다.
- **상수 전파 (Constant Propagation)**
변수가 컴파일 타임에 상수 값임을 알 수 있을 때, 그 값을 사용처에 전파합니다.
const int a = 10;
int b = a + 5; // → int b = 15;
- **데드 코드 제거 (Dead Code Elimination)**
결과가 프로그램의 관찰 가능한 동작에 영향을 주지 않는 코드(변수 할당 포함)를 제거합니다.
- **공통 서브식 제거 (Common Subexpression Elimination)**
동일한 연산이 반복될 때 한 번만 계산하고 결과를 재사용합니다.
- **루프 불변 코드 이동 (Loop-Invariant Code Motion)**
루프 안에서 값이 변하지 않는 연산을 루프 밖으로 빼냅니다.
- **레지스터 할당 (Register Allocation)**
자주 사용되는 변수를 CPU 레지스터에 유지해 메모리 접근을 최소화합니다. (그래프 색칠 + spilling 기법 사용)
- **스칼라 교환 / 배열 스칼라화 (Scalar Replacement / Scalarization)**
배열이나 구조체 요소 중 자주 접근되는 부분을 임시 스칼라 변수로 치환하여 메모리 접근과 주소 계산을 줄입니다.
-O2 / -O3 수준에서 자동으로 적용되지만, 과도한 최적화가 의도하지 않은 동작을 유발할 수 있어 volatile이나 std::atomic 등을 사용해 제한하기도 합니다.Q: 상수 전파에 대해서 자세히
**상수 전파(Constant Propagation)**는 변수가 컴파일 타임에 알려진 상수 값임을 파악한 뒤, 그 값을 모든 사용 지점에 전파하여 연산을 단순화하는 기법입니다.
const int a = 10;
const int b = 20;
int c = a + b; // → int c = 30;
int d = a * b; // → int d = 200;
const int b = 20;
int c = a + b; // → int c = 30;
int d = a * b; // → int d = 200;
상수 전파는 보통 **Reaching Definition 분석**과 결합되어 수행되며,
const가 없어도 변수가 실제로 변경되지 않는다는 것이 증명되면 적용 가능합니다. Q: 상수 전파와 상수 폴딩의 차이?
- **상수 폴딩 (Constant Folding)**
컴파일 타임에 **상수 리터럴**로만 구성된 표현식을 미리 계산합니다.
int y = 5 * 2; // → int y = 10;
- **상수 전파 (Constant Propagation)**
변수에 상수 값이 할당된 사실을 추적해 그 값을 사용처까지 **전파**합니다.
const int x = 5;
int y = x * 2; // → int y = 10;
int y = x * 2; // → int y = 10;
즉, 상수 폴딩은 “리터럴만 있는 식”을 계산하는 것이고, 상수 전파는 “변수에 상수가 들어있는 사실”을 퍼뜨리는 것입니다. 두 기법은 거의 항상 함께 사용되며, 상수 전파 후에 폴딩이 추가로 적용되는 경우가 많습니다.
Q: const 키워드가 없는 변수에도 상수 전파가 되는가?
**네, 가능합니다.**
컴파일러는 정적 분석(특히 데이터 흐름 분석)을 통해 변수가 실제로 변경되지 않는다는 것을 증명하면
컴파일러는 정적 분석(특히 데이터 흐름 분석)을 통해 변수가 실제로 변경되지 않는다는 것을 증명하면
const 없이도 상수 전파를 수행합니다.
int a = 10;
int b = 20;
int c = a + b; // → 가능하면 int c = 30;
int b = 20;
int c = a + b; // → 가능하면 int c = 30;
하지만 분기나 포인터, 함수 호출 등으로 값이 변할 “가능성”이 조금이라도 있으면 안전하게 최적화를 포기합니다.
int a = 10;
if (condition) a = 15;
int c = a + 20; // 상수 전파 불가
if (condition) a = 15;
int c = a + 20; // 상수 전파 불가
Q: 데드 코드 제거에 대해서.
**데드 코드 제거(Dead Code Elimination, DCE)**는 실행 결과에 영향을 주지 않는 코드를 제거하는 기법입니다.
- 절대 도달하지 않는 코드 (unreachable code)
- 쓰여지기만 하고 읽히지 않는 변수 할당
- 호출되지 않는 함수 (링커 단계에서도 적용 가능)
- 쓰여지기만 하고 읽히지 않는 변수 할당
- 호출되지 않는 함수 (링커 단계에서도 적용 가능)
int unused_function() { return 42; } // main에서 호출 안 하면 제거 가능
int main() {
int x = 100; // 사용 안 되면 할당 자체 제거 가능
return 0;
}
int main() {
int x = 100; // 사용 안 되면 할당 자체 제거 가능
return 0;
}
**주의**: side-effect가 있는 연산(입출력, volatile 쓰기, atomic 연산 등)은 절대 데드 코드로 간주되지 않습니다.
Q: 데드 코드 제거가 과도한 최적화로 인해 예상치 못한 동작을 유발하는 예시
데드 코드 제거 자체는 거의 문제를 일으키지 않습니다.
그러나 **volatile 미사용 시** 또는 **side-effect 없는 무한 루프**가 제거되는 경우 문제가 됩니다.
그러나 **volatile 미사용 시** 또는 **side-effect 없는 무한 루프**가 제거되는 경우 문제가 됩니다.
volatile int ready = 0;
while (!ready) {
// busy wait
}
while (!ready) {
// busy wait
}
volatile이 없으면 컴파일러가 루프를 제거하거나 무한 루프로 판단해 최적화할 수 있습니다 → 프로그램이 멈추는 버그 발생 가능.또 다른 대표 사례는 **empty infinite loop**가 제거되는 경우입니다. (C++ 표준상 side-effect 없는 루프는 제거 가능)
Q: 공통 서브식 제거 (CSE)
동일한 연산이 여러 번 등장할 때 한 번만 계산합니다.
int a = x * y;
int b = x * y + z;
int c = x * y * z;
// 최적화 후
int t = x * y;
int a = t;
int b = t + z;
int c = t * z;
int b = x * y + z;
int c = x * y * z;
// 최적화 후
int t = x * y;
int a = t;
int b = t + z;
int c = t * z;
Q: 루프 불변 코드 제거 (Loop-Invariant Code Motion)
루프 안에서 변하지 않는 계산을 루프 밖으로 이동
for (int i = 0; i < n; ++i) {
int x = a * b; // 불변
result[i] = x * i;
}
// 최적화 후
int x = a * b;
for (int i = 0; i < n; ++i) {
result[i] = x * i;
}
int x = a * b; // 불변
result[i] = x * i;
}
// 최적화 후
int x = a * b;
for (int i = 0; i < n; ++i) {
result[i] = x * i;
}
---
Q: 스칼라 교환 (Scalar Replacement)
배열/구조체 요소를 임시 스칼라 변수로 치환 → 메모리 접근 감소 + 레지스터 활용 향상
현대 컴파일러는 주로 **SROA (Scalar Replacement of Aggregates)** 형태로 구조체를 스칼라들로 분해합니다.
사용자가 직접 임시 변수를 만드는 예제는 실제 자동 최적화 모습과 다소 다릅니다.
사용자가 직접 임시 변수를 만드는 예제는 실제 자동 최적화 모습과 다소 다릅니다.
Q: 다른 주요 최적화 기법
- 함수 인라이닝 (Inline Expansion)
- 루프 언롤링 (Loop Unrolling)
- 자동 벡터화 (Auto-Vectorization + SIMD)
- 캐시 친화적 접근 패턴 유도 (타일링, 차원 순서 최적화 등)
- 루프 언롤링 (Loop Unrolling)
- 자동 벡터화 (Auto-Vectorization + SIMD)
- 캐시 친화적 접근 패턴 유도 (타일링, 차원 순서 최적화 등)
**트리 셰이킹(Tree Shaking)**은 C++ 네이티브 컴파일러에서는 거의 쓰이지 않는 용어입니다. 주로 JavaScript 번들러에서 모듈 단위 dead code 제거를 지칭합니다. C++에서는 링커의 dead code elimination이나 section folding 등이 비슷한 역할을 합니다.
Show Comments