GCC 최적화 옵션
GCC로 컴파일하던 중 spdlog 라이브러리에서 컴파일시 -O3 옵션을 사용하는 것을 보고 궁금증이 생겨 찾아보았다.
2026년 수정 안내 이 글에 인용했던 “커널이 -O2를 쓰는 이유”는 부정확했다.
inline키워드가 컴파일러에게 인라인을 강제하지 못한다는 점, 그리고 실제 이유가 무엇인지를 바로잡는다. 결론으로 삼았던 “-O2와 -O3의 파일 크기 차이가 거의 없으니 -O2로 충분하다”는 판단 근거도 최적화 수준을 고르는 기준으로는 부적절해 함께 짚는다.
-O 옵션별 차이
| 옵션 | 특징 |
|---|---|
-O0 |
최적화 없음. 디버깅에 적합. 컴파일이 가장 빠르다 |
-O1 |
기본적인 최적화. 코드 크기와 속도의 균형 |
-O2 |
대부분의 최적화를 켠다. 코드 크기를 크게 늘리지 않는 선에서 속도 개선. 배포 빌드의 사실상 표준 |
-O3 |
-O2에 벡터화, 함수 인라이닝 확대, 공격적인 루프 언롤링 등을 추가 |
-Os |
크기 최적화. -O2에서 크기를 늘리는 최적화를 뺀다 |
-O3가 항상 더 빠른 것은 아니다. 코드가 커지면 명령어 캐시(i-cache) 적중률이 떨어져 오히려 느려지는 경우가 실무에서 드물지 않다. 그래서 “일단 -O3”가 아니라 실제 워크로드로 벤치마크해서 정하는 것이 맞다.
커널이 -O2를 쓰는 진짜 이유
원래 이 글에 인용했던 설명은 “커널이 인라인 함수를 많이 쓰는데 -O3는 이를 함수로 바꿔버려서 -O2를 쓴다”는 것이었는데, 이건 두 가지가 틀렸다.
1. inline 키워드는 애초에 컴파일러를 강제하지 못한다.
C/C++의 inline은 의미가 “이 함수를 인라인 확장해라”가 아니라 “이 정의가 여러 번역 단위에 나타나도 된다(ODR 완화)” 는 링크 관련 지시에 가깝다. 실제로 인라인할지 말지는 애초부터 컴파일러의 휴리스틱과 최적화 수준이 결정한다. -O3가 인라인 함수를 함수로 “되돌린다”는 서술 자체가 inline의 의미를 잘못 이해한 것이다.
리눅스 커널이 인라인을 강제해야 하는 곳에는 inline이 아니라 GCC 확장인 __always_inline(커널 내부적으로는 __always_inline__ 속성)을 쓴다. 이건 최적화 수준과 무관하게 적용된다.
2. 실제 이유는 코드 크기와 명령어 캐시 압박이다.
-O3는 함수 인라이닝 범위 확대, 루프 언롤링, 자동 벡터화 같은 최적화를 추가로 켠다. 이런 최적화는 대체로 코드 크기를 키운다. 커널처럼 다양한 워크로드에서 실행되고 크기가 캐시 적중률에 민감한 코드에서는, 이렇게 커진 코드가 명령어 캐시를 압박해 오히려 전체 성능이 떨어질 수 있다. 커널 공식 문서와 Kconfig도 이 근거로 -O2를 기본으로 삼고 있고, -O3 빌드는 실험적으로만 지원한다.
파일 크기가 아니라 실측으로 판단해야 한다
원래 이 글의 결론은 “-O2와 -O3의 파일 크기가 비슷하니 -O2를 쓰겠다”였는데, 파일 크기는 최적화 수준을 고르는 기준으로 적절하지 않다. -O2/-O3의 차이는 크기보다 실행 경로의 인라이닝·벡터화·분기 예측 힌트 같은 곳에서 나타나고, 이건 실제 워크로드를 실행 시간으로 재봐야 드러난다.
라이브러리가 -O3를 권장한다면, 파일 크기가 아니라 자신의 실제 사용 패턴으로 벤치마크한 뒤 결정하는 것이 좋다. 캐시 지역성이 중요한 커널 코드와, 벡터화 이득이 큰 수치 연산 라이브러리는 최적의 옵션이 다를 수 있다.
댓글 남기기