Featured image of post [Compiler 03] 최적화 플래그: -O2/-O3/-Ofast

[Compiler 03] 최적화 플래그: -O2/-O3/-Ofast

컴파일러 최적화 레벨 -O0~-Ofast의 의미와 켜지는 최적화 종류, -O2 vs -O3 trade-off, 릴리즈/디버그/프로파일 빌드 전략을 다룹니다. 언제 어떤 레벨을 쓸지 판단 기준과 측정 방법을 제시합니다.

최적화 플래그는 코드를 바꾸지 않고도 생성되는 기계어와 성능을 바꾸는 첫 단계입니다. 같은 소스에 -O2-O3만 바꿔도 벤치마크 결과가 수 퍼센트에서 수십 퍼센트까지 달라질 수 있으며, 잘못 선택하면 코드 크기 증가로 오히려 느려지는 회귀도 발생합니다. 이 챕터에서는 GCC/Clang의 -O0~-Ofast와 MSVC의 /O1·/O2·/Ox 의미, trade-off, 언제 어떤 레벨을 쓸지/피할지 판단 기준, 그리고 릴리즈/디버그/프로파일 빌드 전략을 정리합니다.

왜 최적화 플래그인가 (동기)

컴파일러는 소스를 **중간 표현(IR)**으로 바꾼 뒤, 수십 가지 최적화 패스(인라이닝, 루프 최적화, 레지스터 할당, 벡터화 등)를 적용해 기계어를 만듭니다. 어떤 패스를 켤지는 최적화 레벨 한 번으로 제어할 수 있어, 코드 수정 없이 빌드 옵션만으로 성능을 탐색할 수 있습니다. 레벨을 올리면 더 많은 최적화가 켜지지만 컴파일 시간·메모리·코드 크기가 늘고, 드물게 특정 코드에서 성능이 나빠지기도 합니다. 그래서 이 챕터의 핵심은 “무조건 높은 레벨"이 아니라 측정 후 선택입니다.

역사·배경 (최적화 레벨의 등장)

GCC에서는 1990년대부터 -O, -O2 등 최적화 레벨이 도입되었고, -O3는 더 공격적인 최적화를 묶은 것으로 확장되었습니다. GCC 공식 문서는 -O3가 -O2의 모든 최적화에 더해 추가 패스를 켠다고 설명합니다.

“Optimize even more. -O3 turns on all optimizations specified by -O2 and also turns on the -finline-functions, -funswitch-loops, -fpredictive-commoning, -fgcse-after-reload, -ftree-vectorize and other options.” — GCC Manual, Optimize Options

레벨이 올라갈수록 추가로 켜지는 옵션이 문서에 나열되므로, 정확한 목록은 사용 중인 GCC 버전의 문서를 참고하는 것이 좋습니다. -Os(크기 최소화)는 임베디드·제한된 환경을 위해 추가되었고, -Ofast는 GCC 4.x 대에서 부동소수점 완화 옵션을 묶은 레벨로 등장했습니다. Clang은 GCC와 호환되는 레벨 이름을 유지하면서 내부적으로 다른 최적화 순서·휴리스틱을 쓰며, MSVC는 /O1, /O2, /Ox 등 자체 명칭을 사용합니다. 오늘날 대부분의 C/C++ 프로젝트는 -O2 또는 /O2를 릴리즈 기본값으로 두고, 필요 시 -O3를 벤치마크와 함께 검증하는 패턴이 널리 쓰입니다.

최적화 레벨 정의 (GCC / Clang)

최적화 레벨은 “어떤 패스를 얼마나 공격적으로 켤지"를 한 번에 지정하는 옵션입니다. 큰 흐름은 -O0(없음) → -O1(기본) → -O2(균형, 릴리즈 권장) → -O3(공격적) 으로 점점 더 많은 최적화가 켜지고, 여기서 갈라져 나온 -Os는 크기를, -Ofast는 부동소수점 완화를 통한 속도를 우선합니다. 각 레벨의 목적과 켜지는 최적화 방향은 다음과 같습니다.

  • -O0: 최적화 없음. 디버깅 시 변수·소스 라인 대응이 정확하고 컴파일이 빠르지만, 생성 코드는 느립니다. 디버그 빌드 기본값으로 적합합니다.
  • -O1: 기본적인 최적화만 적용. 컴파일 시간을 크게 늘리지 않으면서 일부 성능 이득을 얻을 때 사용합니다. 인라인은 제한적, 루프·레지스터 최적화가 일부 켜집니다.
  • -O2: 대부분의 릴리즈 빌드 권장 레벨입니다. 인라이닝, 루프 최적화, 레지스터 할당 등 대다수 최적화가 켜지고, -O3에 비해 공격적인 변환은 적어 코드 크기·컴파일 시간·회귀 위험이 상대적으로 낮습니다.
  • -O3: -O2에 더해 더 공격적인 인라이닝, 루프 언롤링, 예측 실행 관련 최적화가 추가됩니다. 수치 연산·벡터화에 유리한 코드에서는 -O2 대비 눈에 띄게 빨라질 수 있지만, 코드 크기가 커지고 컴파일 시간·메모리 사용량이 늘며, 드물게 성능 회귀가 나옵니다.
  • -Os: 코드 크기를 줄이는 방향으로 최적화합니다. 인라이닝·언롤링을 억제해 바이너리 크기를 줄이므로, 임베디드·캐시가 극히 제한된 환경에서 고려합니다.
  • -Ofast: -O3에 더해 부동소수점 연산의 표준 준수를 완화합니다(-ffast-math 계열). 수치적으로 더 빠른 코드가 나올 수 있지만, 반올림·연산 순서에 따라 결과가 달라질 수 있어 일반 코드에서는 사용을 피하는 것이 안전합니다.

MSVC 대응

MSVC는 /O1(크기 최소화), /O2(속도 최대화, 기본 릴리즈), /Ox(최대 최적화) 등으로 비슷한 역할을 합니다. /O2가 GCC/Clang의 -O2에 대응하는 기본 릴리즈 설정으로 널리 쓰이며, Visual Studio 프로젝트 속성에서 “Optimization"을 “Maximize Speed (/O2)“로 두면 됩니다.

최적화 레벨별로 켜지는 최적화 (개념)

레벨이 올라갈수록 인라이닝·루프 최적화·벡터화 등이 더 공격적으로 적용됩니다. 개념적으로는 아래와 같습니다.

flowchart LR
  subgraph low ["-O0 / -O1"]
    A["최소 최적화"]
    B["디버깅 용이"]
  end
  subgraph mid ["-O2"]
    C["인라이닝·루프·레지스터"]
    D["일반 릴리즈 권장"]
  end
  subgraph high ["-O3 / -Ofast"]
    E["공격적 인라이닝·언롤링"]
    F["코드 크기·회귀 주의"]
  end
  low --> mid --> high

실제로 켜지는 플래그 목록은 컴파일러·버전마다 다르므로, gcc -O2 -Q --help=optimizers나 문서로 확인합니다(개별 플래그 확인 방법은 아래 「개별 최적화 플래그와 확인 방법」 절 참고).

-O2 vs -O3 trade-off

대부분의 선택은 -O2와 -O3 사이에서 일어나며, 둘의 차이는 단순한 “속도 차이"가 아니라 속도·코드 크기·컴파일 시간·회귀 위험의 묶음입니다. 핵심은 -O3가 항상 빠른 것은 아니라는 점으로, 코드 크기가 커지면 명령어 캐시(I-cache) 압박으로 오히려 느려질 수 있습니다.

  • 속도: 벡터화·인라이닝에 민감한 핫 루프가 있으면 -O3가 유리한 경우가 많습니다. 차이는 워크로드에 따라 0%에 가깝게 나올 수도, 두 자릿수 %까지 나올 수도 있습니다.
  • 코드 크기: -O3는 인라이닝·언롤링으로 텍스트 세그먼트가 커져 I-cache 압박이 생길 수 있어, 작은 핫 루프가 많은 경우 오히려 느려질 수 있습니다.
  • 컴파일 시간: -O3가 더 오래 걸리고, 대형 프로젝트에서는 빌드 시간이 눈에 띄게 늘어납니다.
  • 회귀: -O3에서만 발생하는 회귀는 드물지만 보고되는 편이므로, 릴리즈 전에 동일 벤치마크로 -O2 vs -O3를 비교해 두는 것이 좋습니다.

-Ofast와 부동소수점

-Ofast는 -O3에 -ffast-math 계열 옵션을 더한 레벨입니다. -ffast-math는 -fno-math-errno, -ffinite-math-only, -fno-rounding-math, -fno-signaling-nans, -fassociative-math 등 여러 하위 옵션의 묶음으로, 부동소수점 연산의 표준 준수를 완화합니다. 예를 들어 연관 법칙을 가정하면 (a + b) + ca + (b + c)를 같은 것으로 간주해 순서를 바꿀 수 있어 벡터화가 더 잘 들어가지만, 부동소수점에서는 순서에 따라 결과가 달라질 수 있습니다.

따라서 재현성이 중요한 시뮬레이션·금융·규제 제출 코드에서는 -Ofast를 쓰지 않고 -O3까지로 제한합니다. 신호 처리·이미지 처리처럼 오차 허용 범위가 넓은 도메인에서는 -Ofast로 속도 이득을 취하는 경우가 있습니다. 위험을 줄이려면 -O3에 -ffast-math 전체 대신 -fno-math-errno 같은 개별 옵션만 선택적으로 켜는 방법도 있습니다.

빌드 명령과 크기·시간 비교 (예시)

동일한 소스에 다른 최적화 레벨을 적용해 컴파일 시간과 바이너리 크기를 비교하는 예시입니다.

1
2
3
4
5
6
7
8
9
# 단일 TU 컴파일 시간 비교 (GCC/Clang)
g++ -O0 -c example.cc -o example_o0.o   # 가장 빠름
g++ -O2 -c example.cc -o example_o2.o
g++ -O3 -c example.cc -o example_o3.o # 가장 느림

# 실행 파일 크기 비교 (텍스트 세그먼트)
g++ -O2 example.cc -o app_o2 && size app_o2
g++ -O3 example.cc -o app_o3 && size app_o3
# -O3 쪽 text 영역이 더 클 수 있음

빌드 시스템에서는 빌드 타입으로 레벨을 묶어 관리합니다. CMake의 표준 빌드 타입은 다음과 같습니다.

1
2
3
4
# CMake에서 Release/Debug 설정
cmake -DCMAKE_BUILD_TYPE=Release ..   # 보통 -O3 또는 -O2
cmake -DCMAKE_BUILD_TYPE=Debug ..     # -O0 -g
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..  # -O2 -g

측정과 비교

레벨 선택은 추측이 아니라 측정으로 합니다. 동일한 벤치마크 스위트를 각 플래그로 빌드해 반복 실행하고, 한두 번의 실행이 아니라 평균·표준편차(또는 백분위)를 비교해야 의미 있는 판단이 됩니다. 벤치마크가 핫패스를 잘 커버하지 못하면 레벨 차이가 드러나지 않습니다.

  • 컴파일 시간: 동일 소스에 -O0 / -O2 / -O3를 적용해 측정합니다. 대형 TU에서 차이가 큽니다.
  • 실행 파일 크기: size로 텍스트·데이터·bss 크기를 비교합니다. -Os < -O2 < -O3 순으로 커지는 경향이 있습니다.
  • 벤치마크: 각 플래그 빌드를 반복 실행해 평균·분포를 비교합니다.
  • 회귀 테스트: 릴리즈 브랜치에서는 레벨을 고정하고, 코드 변경 시 동일 벤치마크로 성능 하락이 없는지 CI에서 주기적으로 확인합니다.

수치를 해석하는 예를 들면, 동일 벤치마크를 -O2와 -O3로 각각 10회 실행해 -O2: 100µs, -O3: 85µs를 얻었다면 약 15% 개선입니다. 이때 (1) 표준편차·백분위로 차이가 통계적으로 의미 있는지 확인하고, (2) 같은 스위트의 다른 케이스에서 -O3가 느려진 구간이 없는지 보고, (3) -O3 바이너리의 텍스트 세그먼트가 크게 늘었는지 확인합니다. 모든 구간에서 이득이 있거나 동등하고 크기 증가가 수용 가능하면 -O3를 채택하고, 한 구간이라도 나빠지면 원인(예: I-cache 압박)을 분석하거나 해당 모듈만 -O2로 둡니다. (측정값은 CPU·컴파일러·플래그에 따라 다릅니다.)

간단한 예시로 레벨별 차이 확인

작은 합산 루프 하나로도 레벨 차이를 체감할 수 있습니다. 아래 코드를 -O0와 -O2로 빌드해 실행 시간을 비교해 봅니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// sum.cc: 단순 합산 루프 (벤치마크용 최소 예시)
#include <vector>
#include <cstdint>

uint64_t sum(const std::vector<int>& v) {
  uint64_t s = 0;
  for (size_t i = 0; i < v.size(); ++i)
    s += v[i];
  return s;
}

-O0에서는 루프가 최소 최적화만 받아 상대적으로 느리고, -O2에서는 루프 최적화·인라이닝으로 훨씬 빠른 기계어가 생성됩니다. 실제 측정 시에는 Google Benchmark, nanobench 등으로 반복 실행 후 평균·분포를 보고, 대형 TU에서는 time g++ -O0 -c big.cc vs -O2로 컴파일 시간 차이도 확인할 수 있습니다.

릴리즈/디버그/프로파일 빌드 전략

빌드 목적에 따라 레벨을 나누는 것이 기본 원칙입니다. 디버그는 정확한 단계 실행을, 릴리즈는 성능을, 프로파일 수집은 계측 비용과 대표성의 균형을 우선합니다.

  • 디버그: -O0(또는 -O1)에 -g를 함께 써서 단계 실행·변수 조사에 적합하게 유지합니다.
  • 릴리즈: -O2를 기본으로 하고, 핫 경로가 수치·벡터화에 민감하면 -O3를 시도한 뒤 벤치마크로 검증합니다. 회귀가 없을 때만 -O3를 채택합니다.
  • 프로파일 수집: PGO 1단계처럼 instrumented 빌드에서는 -O2 수준으로 두고, 프로파일 기반 최적화 단계에서 -O3를 쓰는 조합이 흔합니다.

LTO·PGO와의 조합도 같은 원리입니다. LTO(챕터 02)를 쓸 때도 각 TU는 먼저 -O2/-O3로 IR을 생성하고 링크 시점에 합쳐 추가 최적화하므로 -O2 -flto 또는 -O3 -flto 조합이 일반적이며, PGO(챕터 03)는 1단계를 -O2로, 2단계를 -O3로 두는 패턴이 흔합니다. 어느 경우든 동일 벤치마크로 측정해 회귀가 없는지 확인합니다.

빌드 시스템별로는 빌드 타입을 다음과 같이 매핑하고, CI에서는 릴리즈 빌드로 회귀를 측정합니다.

빌드 시스템릴리즈(-O2)디버그(-O0)RelWithDebInfo
CMakeCMAKE_BUILD_TYPE=ReleaseCMAKE_BUILD_TYPE=DebugCMAKE_BUILD_TYPE=RelWithDebInfo
Mesonbuildtype: ‘release’buildtype: ‘debug’buildtype: ‘debugoptimized’
MakeCXXFLAGS=-O2CXXFLAGS=-O0 -gCXXFLAGS=-O2 -g

CMake의 Release 기본이 -O3인 경우도 있으므로, 프로젝트에서 add_compile_options(-O2) 등으로 명시적으로 고정해 두면 환경에 덜 흔들립니다.

개별 최적화 플래그와 확인 방법

최적화 레벨은 수십 개의 개별 -f* 플래그를 묶은 것입니다. -O2는 대략 -finline·-foptimize-sibling-calls 등을 켜고, -O3는 여기에 -finline-functions(더 공격적 인라이닝)·-funswitch-loops·-ftree-vectorize 등이 더해진 묶음으로 이해할 수 있습니다. 실무에서는 보통 -O2 또는 -O3만 지정하고 개별 플래그를 건드리지 않는 편이 유지보수에 유리하지만, 특정 최적화에서만 버그가 재현된다면 해당 최적화만 끄는 -fno-* 플래그로 회귀를 피할 수 있습니다.

어떤 최적화가 실제로 켜지는지 확인하려면 컴파일러 도움말을 사용합니다.

  • GCC: gcc -O2 -Q --help=optimizers로 -O2에서 켜지는/꺼지는 최적화 목록을 보고, -O3와 diff해 차이를 비교합니다.
  • Clang: clang -O2 -mllvm -debug-pass=Structure(버전에 따라 다름)로 패스 구성을 보거나 공식 문서로 -O2/-O3 차이를 확인합니다.
  • MSVC: 프로젝트 속성 “Optimization” 하위에서 “Inline Function Expansion”, “Enable Intrinsic Functions” 등 개별 옵션을 확인합니다.

한눈에 보기: 최적화 레벨 비교

지금까지의 정의·트레이드오프를 한 표로 정리하면 다음과 같습니다. 인라이닝·벡터화 열은 “얼마나 공격적인지"의 개념적 방향이며, 실제 켜지는 플래그는 컴파일러·버전에 따라 다릅니다.

레벨목적인라이닝/벡터화코드 크기컴파일 시간권장 용도
-O0없음거의 없음작음빠름디버그 빌드
-O1기본제한적보통보통빠른 빌드 + 일부 이득
-O2균형일반적보통보통일반 릴리즈 기본
-O3최대 속도적극적·언롤링느림수치/벡터화 핫패스, 벤치 검증 후
-Os크기 최소억제작음보통임베디드·제한된 캐시
-Ofast수치 최대-O3+추가느림신호/과학 계산만, 재현성 불필요 시

판단 기준: 언제 쓸지 / 언제 피할지

상황권장비권장
일반 릴리즈 빌드-O2-O0, -Ofast(일반 코드)
수치·벡터화가 중요한 핫 루프-O3 (벤치로 검증 후)-O3 무검증 채택
디버깅·단계 실행-O0 또는 -O1-O2 이상
임베디드·바이너리 크기 제한-Os 또는 -O2-O3
금융·규제·재현성 중요-O2 또는 -O3 (fast-math 없음)-Ofast
PGO 1단계(instrumented)-O2 수준-O0(너무 느림), -O3(과도)

적용 체크리스트: (1) 릴리즈 기본은 -O2로 두고, -O3는 벤치마크로 이득과 회귀를 확인한 뒤 도입한다. (2) -Ofast는 부동소수점 재현성이 필요 없는 도메인에서만 사용한다. (3) CI에서 릴리즈 빌드로 성능 회귀를 주기적으로 측정한다.

실전 시나리오: -O2에서 -O3 전환 검토

릴리즈가 -O2로 설정되어 있고 핫패스에 수치 연산·루프가 많은 프로젝트에서 -O3 전환을 검토하는 흐름은 다음과 같습니다.

  1. 벤치마크 준비: 핫패스를 잘 커버하는 벤치마크(또는 통합 테스트의 성능 구간)를 확보합니다.
  2. 빌드: 동일 소스로 -O2와 -O3 각각 빌드해 두 바이너리를 만듭니다.
  3. 실행·측정: 같은 벤치마크를 여러 번 실행해 평균·표준편차(또는 백분위)를 기록합니다.
  4. 크기 확인: size로 텍스트 세그먼트 증가를 봅니다. 크게 늘고 지연이 중요한 경로가 여러 개면 I-cache 압박 가능성을 염두에 둡니다.
  5. 판단: 속도 이득이 명확하고 다른 구간의 회귀가 없으면 -O3로 전환하고, 이득이 미미하거나 회귀가 있으면 -O2를 유지합니다.
  6. CI 반영: 선택한 레벨로 릴리즈 빌드 후, CI에서 -O2와 -O3 결과를 함께 수집해 -O3에서만 느려지는 회귀를 자동으로 감지합니다. 일정 비율 이상 나빠지면 실패로 처리하고 빌드 로그·벤치 결과를 아티팩트로 보관합니다.

자주 하는 실수

  • -O3를 검증 없이 릴리즈에 넣는 경우: 일부 코드에서 코드 크기 증가로 느려질 수 있으므로, 동일 벤치마크로 -O2 vs -O3를 비교한 뒤 채택한다.
  • -Ofast를 일반 비즈니스 로직에 쓰는 경우: 부동소수점 결과가 바뀔 수 있어, 재현성·규제가 중요한 코드에서는 사용하지 않는다. 테스트가 비트 단위 기대값을 비교한다면 허용 오차(epsilon)를 두거나 해당 모듈만 -O3로 둔다.
  • 디버그 빌드에 -O2 이상을 쓰는 경우: 변수가 “optimized out"으로 사라지거나 라인 대응이 어긋나 디버깅이 어려워진다. 디버깅 시에는 -O0 또는 -O1 + -g를 유지하고, 릴리즈 동작 재현이 필요하면 RelWithDebInfo(-O2 -g)를 쓰되 일부 변수는 여전히 보이지 않을 수 있음을 받아들인다.

비판적 시각: 한계와 트레이드오프

최적화 레벨은 높을수록 좋다가 아닙니다. -O3는 코드 크기를 키워 I-cache 미스를 늘릴 수 있고, -Ofast는 수치 결과를 바꿉니다. “릴리즈 = -O3"로 고정하기보다, 프로젝트의 핫패스와 벤치마크로 한 번씩 측정한 뒤 선택하는 것이 안전합니다. 레거시 프로젝트라면 이미 -O2로 안정적으로 돌고 있을 때 무리하게 -O3로 올리지 말고, 성능 이득이 명확할 때만 단계적으로 전환합니다. 이미 -O3인데 특정 TU만 문제를 일으키면 그 TU만 -O2로 컴파일하도록 예외를 둘 수 있습니다(CMake: set_source_files_properties(... COMPILE_FLAGS -O2)).

자주 묻는 질문

  • Q: 릴리즈는 무조건 -O3가 맞나요? A: 아닙니다. -O2를 기본으로 하고, 벤치마크로 -O3 이득이 있고 회귀가 없을 때만 -O3를 씁니다.
  • Q: -Os와 -O2 중 무엇을 써야 하나요? A: 바이너리 크기나 I-cache가 극히 제한된 환경(일부 임베디드)이면 -Os를, 그렇지 않으면 보통 -O2가 유리합니다.
  • Q: -Ofast를 썼을 때 테스트가 실패합니다. A: 부동소수점 비교가 비트 단위로 맞지 않을 수 있습니다. 허용 오차를 두거나, 해당 모듈만 -O3로 두고 -Ofast를 제거합니다.
  • Q: CMake Release가 -O3인데 -O2로 고정하려면? A: set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG")처럼 Release 플래그를 덮어씁니다.
  • Q: 컴파일 시간을 줄이려면? A: -O3 대신 -O2를 쓰면 줄어듭니다. 캐시·증분 빌드는 챕터 12에서 다룹니다.

Tr.02(인라이닝)과의 연계

Low-latency C++ Language Optimization 트랙(Tr.02)에서는 인라이닝 유도를 다룹니다. 인라이닝이 되려면 컴파일러가 호출 지점에서 함수 정의를 볼 수 있어야 하고 최적화 레벨이 -O1 이상이어야 하며, -O0에서는 인라이닝이 거의 켜지지 않습니다. 따라서 “인라이닝을 유도하는 코드 배치·final·LTO"는 -O2 또는 -O3와 함께 쓸 때 효과가 납니다. 이 챕터는 “어떤 레벨을 켤지"를, Tr.02은 “그 레벨에서 인라이닝이 실제로 일어나도록 코드를 어떻게 쓸지"를 다룹니다.

학습 성과 목표와 평가 기준

이 글을 읽은 후 다음을 할 수 있어야 합니다. -O0~-Ofast와 MSVC /O1·/O2·/Ox의 의미·권장 용도를 설명하고, -O2 vs -O3 trade-off(속도·코드 크기·컴파일 시간·회귀)를 근거로 레벨을 선택하며, -Ofast의 부동소수점 위험을 들어 언제 피할지 판단하고, 릴리즈/디버그/프로파일 빌드 전략을 설계해 CI에서 회귀를 측정할 수 있습니다. 아래 항목으로 스스로 점검해 봅니다.

  • -O2와 -O3를 선택할 때 고려할 네 요소(속도·코드 크기·컴파일 시간·회귀)를 나열할 수 있는가?
  • -Ofast를 써도 되는 경우와 피해야 하는 경우를 구분할 수 있는가?
  • 디버그와 릴리즈에 서로 다른 레벨을 적용하는 이유를 설명할 수 있는가?
  • 동일 벤치마크로 -O2 vs -O3를 비교하는 절차를 단계별로 말할 수 있는가?
  • CI에서 성능 회귀를 감지하려면 어떤 빌드·실행·비교가 필요한지 말할 수 있는가?
  • MSVC의 /O1·/O2·/Ox가 GCC/Clang의 어떤 레벨에 대응하는지 설명할 수 있는가?

용어 정리

용어설명
-O0~-O3GCC/Clang 최적화 레벨; 숫자가 클수록 더 많은·공격적인 최적화 적용
-Os크기 최소화 방향 최적화; 인라이닝·언롤링 억제
-Ofast-O3 + 부동소수점 표준 완화(-ffast-math 계열)
IRIntermediate Representation; 컴파일러 내부 중간 표현
I-cacheInstruction cache; 명령어 캐시; 코드 크기가 크면 미스 증가 가능
회귀최적화 레벨을 올렸을 때 특정 코드에서 성능이 나빠지는 현상
RelWithDebInfoRelease with Debug Info; -O2 수준 + 디버그 심볼

참고 자료

핵심 요약

항목요약
기본 권장일반 릴리즈는 -O2; 수치·벡터화 핫패스는 -O3를 벤치 검증 후 고려
-Ofast부동소수점 표준 완화; 금융·규제·재현성 중요 시 사용 금지
검증동일 벤치마크로 -O2 vs -O3 비교, 회귀 없을 때만 -O3 채택
디버그-O0 또는 -O1 + -g

다음 장에서는

**LTO(Link-Time Optimization)**와 ThinLTO 개념, GCC/Clang/MSVC에서의 활성화 방법, LTO on/off 성능·크기 검증을 다룹니다.

LTO와 ThinLTO 실전 적용 (챕터 02)