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