const int a = 10; int b = a + 5; // 컴파일러는 이 연산을 'int b = 15;'로 최적화할 수 있습니다.
const int a = 10; const int b = 20; int c = a + b; int d = a * b;
int c = 30; int d = 200;
const int x = 5; int y = x * 2;
int y = 5 * 2;
const
키워드가 없는 변수에 대해서도 적용될 수 있습니다. 컴파일러는 코드를 분석하고 변수의 값이 변경되지 않는 경우에도 상수 전파를 수행할 수 있습니다. 예를 들어, 다음과 같은 코드가 있다고 가정해봅시다.int a = 10; int b = 20; int c = a + b; int d = a * b;
a
와 b
의 값이 변경되지 않는다는 것을 확인하고, 상수 전파 최적화를 적용할 수 있습니다. 따라서 최적화된 코드는 다음과 같이 됩니다. int c = 30; int d = 200;
int a = 10; int b = 20; if (some_condition) { a = 15; } int c = a + b;
some_condition
에 따라 변수 a
의 값이 변경될 수 있기 때문에, 컴파일러는 c = a + b;
연산에 대한 상수 전파 최적화를 수행하지 않을 것입니다.int square(int x) { return x * x; } int main() { int a = 10; int b = 20; int result = a + b; return 0; }
square
함수는 main
함수에서 호출되지 않습니다. 따라서 컴파일러는 데드 코드 제거를 통해 square
함수를 최종 실행 파일에서 제거할 수 있습니다.result
는 main
함수의 반환 값에 영향을 주지 않으므로, 해당 변수에 대한 할당 또한 제거할 수 있습니다.#include <iostream> int global_var = 0; void do_something() { // Some complex operation std::cout << "Doing something..." << std::endl; } int main() { global_var = 10; if (global_var != 10) { do_something(); } return 0; }
do_something()
함수는 global_var
가 10이 아닌 경우에만 호출되어야 합니다. 그러나 과도한 최적화로 인해 컴파일러가 if (global_var != 10)
조건문을 제거하고 do_something()
함수를 항상 호출하는 코드로 변경할 수 있습니다.int a = x * y; int b = x * y + z; int c = x * y * z;
x * y
가 3개의 연산에서 모두 사용되고 있습니다. 컴파일러가 공통 서브식 제거를 적용하면 다음과 같이 최적화된 코드를 생성합니다. int temp = x * y; int a = temp; int b = temp + z; int c = temp * z;
x * y
연산이 한 번만 수행되며, 그 결과를 여러 번 재사용하고 있습니다. 이로 인해 프로그램의 실행 시간이 단축되고 메모리 접근을 줄일 수 있습니다.for (int i = 0; i < n; ++i) { int x = a * b; int y = x * i; result[i] = y; }
int x = a * b;
는 루프 불변 코드입니다. 왜냐하면 a
, b
및 x
값이 루프 내에서 변하지 않기 때문입니다. 컴파일러가 루프 불변 코드 제거를 적용하면 다음과 같이 최적화된 코드를 생성합니다. int x = a * b; for (int i = 0; i < n; ++i) { int y = x * i; result[i] = y; }
a * b
연산이 루프 밖으로 이동하여 한 번만 수행되며, 그 결과가 루프 내에서 재사용됩니다. 이로 인해 프로그램의 실행 시간이 단축됩니다.struct Point { int x; int y; }; void processPoints(Point* points, int numPoints) { int sumX = 0; int sumY = 0; for (int i = 0; i < numPoints; i++) { sumX += points[i].x; sumY += points[i].y; } }
pointsi.x
와 pointsi.y
는 각각 루프에서 반복적으로 사용되는 배열 요소입니다. 컴파일러는 스칼라 교환을 통해 이를 스칼라 변수로 대체하고, 불필요한 메모리 액세스를 줄일 수 있습니다.void processPoints(Point* points, int numPoints) { int sumX = 0; int sumY = 0; for (int i = 0; i < numPoints; i++) { int tempX = points[i].x; int tempY = points[i].y; sumX += tempX; sumY += tempY; } }
tempX
와 tempY
는 스칼라 변수로 대체된 pointsi.x
와 pointsi.y
입니다. 이렇게 최적화를 수행하면, 메모리 액세스 오버헤드가 줄어들고 프로그램 성능이 향상됩니다.__builtin_expect
함수를 사용하여 분기 예측 힌트를 제공할 수 있습니다.inline int add(int a, int b) { return a + b; } int main() { int result = add(3, 4); return 0; }
int main() { int result = 3 + 4; return 0; }
add
함수 호출이 없고, 대신 함수 본문의 코드인 3 + 4
가 직접 사용되고 있습니다. 이로 인해 프로그램의 실행 속도가 향상됩니다.void add_arrays(int* result, int* a, int* b, int n) { for (int i = 0; i < n; ++i) { result[i] = a[i] + b[i]; } }
#include <immintrin.h> // Intel Intrinsics를 사용하기 위한 헤더 void add_arrays(int* result, int* a, int* b, int n) { int i = 0; for (; i < n - 4; i += 4) { __m128i a_vec = _mm_loadu_si128((__m128i*)&a[i]); __m128i b_vec = _mm_loadu_si128((__m128i*)&b[i]); __m128i sum_vec = _mm_add_epi32(a_vec, b_vec); _mm_storeu_si128((__m128i*)&result[i], sum_vec); } // 벡터화되지 않은 나머지 요소 처리 for (; i < n; ++i) { result[i] = a[i] + b[i]; } }
#include <iostream> int main() { const int rows = 1000; const int cols = 1000; int matrix[rows][cols]; // 데이터 초기화 for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { matrix[i][j] = i * j; } } int sum = 0; // 행 우선(row-major) 순서로 2차원 배열에 접근하여 데이터 지역성을 활용합니다. for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { sum += matrix[i][j]; } } std::cout << "Sum: " << sum << std::endl; return 0; }
main
함수 또는 엔트리 포인트)에서 시작하여 모든 의존성을 따라가며 사용되는 심볼을 식별합니다.#include <iostream> int main() { int sum = 0; for (int i = 0; i < 8; ++i) { sum += i; } std::cout << "Sum: " << sum << std::endl; return 0; }
#include <iostream> int main() { int sum = 0; int i; for (i = 0; i < 8; i += 2) { sum += i; sum += i + 1; } std::cout << "Sum: " << sum << std::endl; return 0; }
-O2
또는 -O3
최적화 레벨을 사용하면 루프 언롤링과 같은 최적화를 자동으로 적용합니다.