C++ 컴파일 타임 최적화 | constexpr·PCH·모듈·ccache·Unity 빌드 [#15-3]
이 글의 핵심
C++ 컴파일 타임 최적화에 대해 정리한 개발 블로그 글입니다. 룩업 테이블(미리 계산해 둔 값 배열—인덱스로 바로 결과를 찾을 때 사용)을 런타임(실행 중)에 초기화하고 있었습니다. 하지만 값은 항상 같았습니다. constexpr과 if constexpr을 쓰면 "항상 같은 값"이나… 개념과 예제 코드를 단계적으로 다루며, 실무·학습에 참고할 수 있도록 구성했습니다. 관련 키워드: C++…
들어가며: 매번 같은 계산을 반복한다
”프로그램 시작할 때마다 똑같은 테이블을 계산해요”
룩업 테이블(미리 계산해 둔 값 배열—인덱스로 바로 결과를 찾을 때 사용)을 런타임(실행 중)에 초기화하고 있었습니다. 하지만 값은 항상 같았습니다.
constexpr과 if constexpr을 쓰면 “항상 같은 값”이나 “타입에 따라 다른 분기”를 컴파일 타임에 처리해서, 런타임 비용을 없애고 코드 경로를 단순하게 유지할 수 있습니다. 다만 컴파일 시간이 늘어나므로, 큰 메타프로그래밍은 모듈화해서 필요한 부분만 켜 두는 것이 실무에서 유리합니다.
추가 문제 시나리오
시나리오 1: 빌드가 10분 이상 걸려요
헤더 하나 수정하면 전체 리빌드가 돼서, 작은 수정에도 긴 대기 시간이 발생합니다. PCH·모듈·ccache로 해결할 수 있습니다.
시나리오 2: 템플릿 헤더가 너무 커서 컴파일이 느려요
<iostream>, <vector> 등이 거의 모든 TU에 포함되어 매번 파싱됩니다. PCH로 한 번만 컴파일해 재사용하면 됩니다.
시나리오 3: CI에서 매번 클린 빌드
PR마다 15분 이상 빌드, 캐시 없음. ccache·병렬 빌드로 빌드 시간을 크게 줄일 수 있습니다.
시나리오 4: switch에서 문자열 비교
런타임에 strcmp 반복 호출 대신, constexpr 해시로 컴파일 타임에 상수로 변환해 O(1) 비교가 가능합니다.
시나리오 5: 타입에 따라 다른 직렬화
POD 타입은 바이너리 복사, 복잡한 타입은 직렬화 함수 호출. if constexpr로 컴파일 타임에 분기하면 됩니다.
문제의 코드:
std::array<int, 256> lookupTable;
void initTable() {
for (int i = 0; i < 256; ++i) {
lookupTable[i] = i * i; // 매번 계산
}
}
int main() {
initTable(); // 프로그램 시작마다 실행
// ...
}
constexpr로 해결:
constexpr std::array<int, 256> makeLookupTable() {
std::array<int, 256> table{};
for (int i = 0; i < 256; ++i) {
table[i] = i * i; // ✅ 컴파일 타임에 계산
}
return table;
}
constexpr auto lookupTable = makeLookupTable();
int main() {
// 테이블이 이미 준비됨 (초기화 비용 0)
}
주의사항: 컴파일 타임 계산 한도(constexpr 단계 제한)에 걸리면 빌드가 실패하므로, 거대한 메타프로그래밍은 단계적으로 나눕니다.
이 글을 읽으면:
- constexpr로 컴파일 타임 계산을 할 수 있습니다.
- if constexpr로 조건 분기를 최적화할 수 있습니다.
- 템플릿 메타프로그래밍의 기초를 이해할 수 있습니다.
- PCH·모듈·ccache·Unity 빌드로 빌드 시간을 단축할 수 있습니다.
- 자주 발생하는 에러와 프로덕션 패턴을 알 수 있습니다.
목차
- constexpr 기초
- constexpr 함수
- if constexpr (C++17)
- consteval (C++20)
- 실전 활용
- PCH(미리 컴파일된 헤더)
- C++20 모듈
- ccache로 재빌드 가속
- Unity 빌드
- 자주 발생하는 에러와 해결법
- 성능 최적화 팁
- 프로덕션 패턴
1. constexpr 기초
constexpr 변수
constexpr int x = 10; // 컴파일 타임 상수
constexpr int y = x * 2; // 20
// 배열 크기로 사용 가능
int arr[x]; // ✅ OK
// 일반 const는 불가능할 수 있음
const int z = getInput(); // 런타임 값
// int arr2[z]; // ❌ 에러 (컴파일 타임 상수 아님)
const vs constexpr
const int a = 10; // 컴파일 타임 또는 런타임
constexpr int b = 10; // 반드시 컴파일 타임
const int c = getInput(); // ✅ OK (런타임)
// constexpr int d = getInput(); // ❌ 에러
2. constexpr 함수
기본 사용법
constexpr int square(int x) {
return x * x;
}
int main() {
// 컴파일 타임 계산
constexpr int a = square(5); // 25 (컴파일 타임)
// 런타임 계산도 가능
int input;
std::cin >> input;
int b = square(input); // 런타임
}
재귀 함수
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int f5 = factorial(5); // 120 (컴파일 타임)
// 배열 크기로 사용
int arr[factorial(4)]; // int arr[24]
}
복잡한 계산
constexpr int fibonacci(int n) {
if (n <= 1) return n;
int a = 0, b = 1;
for (int i = 2; i <= n; ++i) {
int temp = a + b;
a = b;
b = temp;
}
return b;
}
constexpr int fib10 = fibonacci(10); // 55 (컴파일 타임)
constexpr 클래스
class Point {
int x, y;
public:
constexpr Point(int x, int y) : x(x), y(y) {}
constexpr int getX() const { return x; }
constexpr int getY() const { return y; }
constexpr int distanceSquared() const {
return x * x + y * y;
}
};
int main() {
constexpr Point p(3, 4);
constexpr int dist = p.distanceSquared(); // 25 (컴파일 타임)
}
3. if constexpr (C++17)
컴파일 타임 분기
template <typename T>
void process(T value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "Integer: " << value << "\n";
} else if constexpr (std::is_floating_point_v<T>) {
std::cout << "Float: " << value << "\n";
} else {
std::cout << "Other: " << value << "\n";
}
}
int main() {
process(42); // Integer: 42
process(3.14); // Float: 3.14
process("hello"); // Other: hello
}
일반 if vs if constexpr
template <typename T>
void func(T value) {
// 일반 if: 두 분기 모두 컴파일됨
if (std::is_integral_v<T>) {
value++; // T가 string이면 컴파일 에러
}
// if constexpr: 선택된 분기만 컴파일
if constexpr (std::is_integral_v<T>) {
value++; // T가 string이면 이 코드 무시됨
}
}
재귀 종료
template <typename T>
void print(T value) {
std::cout << value << "\n";
}
template <typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << " ";
if constexpr (sizeof...(rest) > 0) {
print(rest...); // 재귀
} else {
std::cout << "\n";
}
}
int main() {
print(1, 2, 3, "hello", 4.5);
}
4. consteval (C++20)
반드시 컴파일 타임
consteval int square(int x) {
return x * x;
}
int main() {
constexpr int a = square(5); // ✅ OK (컴파일 타임)
int input = 10;
// int b = square(input); // ❌ 에러: 런타임 호출 불가
}
constexpr vs consteval
constexpr int func1(int x) {
return x * 2; // 컴파일 타임 또는 런타임
}
consteval int func2(int x) {
return x * 2; // 반드시 컴파일 타임
}
int main() {
constexpr int a = func1(5); // ✅ 컴파일 타임
int input = 10;
int b = func1(input); // ✅ 런타임
constexpr int c = func2(5); // ✅ 컴파일 타임
// int d = func2(input); // ❌ 에러
}
5. 실전 활용
패턴 1: 컴파일 타임 해시
constexpr uint32_t hash(const char* str) {
uint32_t hash = 5381;
while (*str) {
hash = ((hash << 5) + hash) + *str++;
}
return hash;
}
int main() {
// switch에서 문자열 비교
constexpr uint32_t cmdOpen = hash("open");
constexpr uint32_t cmdClose = hash("close");
uint32_t cmd = hash(getUserInput());
switch (cmd) {
case cmdOpen:
openFile();
break;
case cmdClose:
closeFile();
break;
}
}
패턴 2: 컴파일 타임 배열 생성
template <size_t N>
constexpr std::array<int, N> makeSquares() {
std::array<int, N> arr{};
for (size_t i = 0; i < N; ++i) {
arr[i] = i * i;
}
return arr;
}
constexpr auto squares = makeSquares<100>();
int main() {
std::cout << squares[10] << "\n"; // 100 (이미 계산됨)
}
패턴 3: 타입 특성 확인
template <typename T>
constexpr bool isTrivial() {
return std::is_trivially_copyable_v<T> &&
std::is_trivially_destructible_v<T>;
}
template <typename T>
void serialize(const T& value) {
if constexpr (isTrivial<T>()) {
// POD 타입: 바이너리 복사
write(&value, sizeof(T));
} else {
// 복잡한 타입: 직렬화 함수 호출
value.serialize();
}
}
패턴 4: 컴파일 타임 문자열 처리
constexpr size_t stringLength(const char* str) {
size_t len = 0;
while (str[len] != '\0') {
++len;
}
return len;
}
constexpr bool startsWith(const char* str, const char* prefix) {
while (*prefix) {
if (*str++ != *prefix++) {
return false;
}
}
return true;
}
int main() {
constexpr size_t len = stringLength("Hello"); // 5
constexpr bool result = startsWith("Hello", "Hel"); // true
}
패턴 5: 컴파일 타임 팩토리얼 테이블
template <size_t N>
constexpr std::array<long long, N> makeFactorials() {
std::array<long long, N> arr{};
arr[0] = 1;
for (size_t i = 1; i < N; ++i) {
arr[i] = arr[i - 1] * i;
}
return arr;
}
constexpr auto factorials = makeFactorials<20>();
int main() {
std::cout << "10! = " << factorials[10] << "\n"; // 즉시 출력
}
6. PCH(미리 컴파일된 헤더)
PCH란?
자주 쓰는 헤더들을 한 번만 컴파일해 .gch(GCC) 또는 .pch(Clang/MSVC)로 저장하고, 각 TU에서 이 바이너리를 재사용합니다. 헤더 파싱 시간을 30~50% 단축할 수 있습니다.
flowchart LR
subgraph Before["PCH 없음"]
A1[main.cpp] --> A2[iostream 파싱]
B1[foo.cpp] --> B2[iostream 파싱]
C1[bar.cpp] --> C2[iostream 파싱]
end
subgraph After["PCH 사용"]
D1[pch.h.gch] --> D2[한 번만 생성]
E1[main.cpp] --> E2[PCH 로드]
F1[foo.cpp] --> F2[PCH 로드]
G1[bar.cpp] --> G2[PCH 로드]
end
PCH 헤더 작성
// pch.h - 미리 컴파일할 헤더
#ifndef PCH_H
#define PCH_H
// 자주 쓰는 표준 라이브러리 (변경 거의 없음)
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <algorithm>
#include <chrono>
// 프로젝트 공통 헤더
#include "config.h"
#include "types.h"
#endif
GCC에서 PCH 생성 및 사용
# 1. PCH 생성
g++ -x c++-header -std=c++20 -O2 -o pch.h.gch pch.h
# 2. 소스 컴파일 시 PCH 사용
g++ -std=c++20 -O2 -include pch.h -c main.cpp -o main.o
CMake에서 PCH 자동화
# CMakeLists.txt - PCH 지원
cmake_minimum_required(VERSION 3.16)
project(MyApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
# PCH 타겟
add_library(pch_header INTERFACE)
target_precompile_headers(pch_header INTERFACE
<iostream>
<string>
<vector>
<memory>
"config.h"
)
add_executable(app main.cpp foo.cpp bar.cpp)
target_link_libraries(app PRIVATE pch_header)
target_precompile_headers(app REUSE_FROM pch_header)
주의: PCH 생성 시 -O2, -DDEBUG 등이 TU와 완전히 동일해야 합니다. PCH 헤더는 파일 최상단에 포함해야 합니다.
7. C++20 모듈
모듈 vs 헤더
| 항목 | #include 헤더 | C++20 모듈 |
|---|---|---|
| 파싱 횟수 | TU마다 반복 | 한 번만 (BMI 캐시) |
| 전이적 노출 | 전체 노출 | export된 것만 |
| 템플릿 인스턴스 | TU마다 반복 | 한 번만 |
| 빌드 순서 | 순서 무관 | 모듈 의존성 순서 필요 |
모듈 기본 예제
math.mpp (모듈 구현):
// math.mpp - C++20 모듈
module;
#include <cmath> // 모듈 선언 전에만 전통적 #include
export module math;
export double sqrt_safe(double x) {
return x >= 0 ? std::sqrt(x) : 0.0;
}
export template<typename T>
T clamp(T v, T lo, T hi) {
return v < lo ? lo : (v > hi ? hi : v);
}
main.cpp (모듈 사용):
import math; // #include 대신 import
#include <iostream>
int main() {
std::cout << sqrt_safe(2.0) << '\n';
std::cout << clamp(42, 0, 100) << '\n';
return 0;
}
CMake에서 모듈 빌드
# CMakeLists.txt - C++20 모듈 지원
cmake_minimum_required(VERSION 3.28)
project(MyApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 모듈 파일을 먼저 컴파일 (의존성 순서)
add_library(math MODULE math.mpp)
target_compile_features(math PUBLIC cxx_std_20)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE math)
주의: GCC 14+, Clang 18+, MSVC 2022 17.5+에서 모듈 지원이 안정화됨.
8. ccache로 재빌드 가속
ccache 동작 원리
flowchart TB
subgraph Input["입력"]
S[소스 + 컴파일 옵션]
end
subgraph CCache["ccache"]
H[해시 계산]
L{캐시<br/>존재?}
R[캐시에서 복원]
C[실제 컴파일]
S2[캐시 저장]
end
S --> H --> L
L -->|Hit| R
L -->|Miss| C --> S2
R --> O[오브젝트 출력]
C --> O
소스+옵션 해시로 캐시 조회 → Hit이면 컴파일 생략, Miss면 컴파일 후 저장.
ccache 설치 및 설정
# Ubuntu/Debian
sudo apt install ccache
# macOS
brew install ccache
# PATH에 ccache 우선 배치
export PATH="/usr/lib/ccache:$PATH"
# 또는
export CC="ccache gcc"
export CXX="ccache g++"
CMake + ccache
# CMakeLists.txt
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
endif()
# 또는 빌드 시
cmake -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -B build
cmake --build build -j8
ccache 통계 확인
ccache -s
# 출력 예:
# cache hit (direct) 1234
# cache hit (preprocessed) 56
# cache miss 89
# cache hit rate 93.54 %
9. Unity 빌드
Unity 빌드란?
여러 .cpp를 하나로 합쳐 컴파일 횟수를 줄입니다. 헤더 중복 파싱을 줄여 40~60% 단축 가능합니다.
수동 Unity 빌드
// unity.cpp - 여러 cpp를 한 번에 컴파일
#include "file1.cpp"
#include "file2.cpp"
#include "file3.cpp"
#include "file4.cpp"
g++ -O2 unity.cpp -o myapp
CMake Unity 빌드
# CMakeLists.txt
set(CMAKE_UNITY_BUILD ON)
add_executable(app main.cpp foo.cpp bar.cpp baz.cpp)
# → 내부적으로 하나의 unity_*.cpp로 합쳐서 컴파일
주의: static 변수 이름 충돌, 매크로 전역 오염 등에 유의. 익명 네임스페이스 사용 권장.
// ✅ static 대신 익명 네임스페이스
namespace {
int counter = 0;
}
10. 자주 발생하는 에러와 해결법
문제 1: PCH “file not found” 또는 “invalid precompiled header”
원인: PCH 생성 옵션과 TU 컴파일 옵션이 다름.
# ❌ 잘못된 예: PCH는 -O0, TU는 -O2
g++ -x c++-header -O0 -o pch.h.gch pch.h
g++ -O2 -include pch.h -c main.cpp # 에러!
해결:
# ✅ 옵션 일치
g++ -x c++-header -std=c++20 -O2 -DNDEBUG -o pch.h.gch pch.h
g++ -std=c++20 -O2 -DNDEBUG -include pch.h -c main.cpp
문제 2: PCH 헤더 앞에 다른 #include가 있음
원인: PCH는 반드시 첫 번째 포함되어야 함.
// ❌ 잘못된 예
#include "other.h" // PCH보다 먼저!
#include "pch.h"
// ✅ 올바른 예
#include "pch.h"
#include "other.h"
문제 3: constexpr 함수에서 “non-constant expression” 에러
원인: constexpr 함수 내에서 허용되지 않는 연산 사용.
// ❌ 잘못된 예
constexpr int bad() {
static int x = 0; // static 금지
return x++;
}
// ❌ 잘못된 예
constexpr int bad2() {
throw std::runtime_error("error"); // 예외 금지
}
해결:
// ✅ C++14: 루프, 변수 선언 가능
constexpr int good(int n) {
int result = 0;
for (int i = 0; i < n; ++i) {
result += i;
}
return result;
}
문제 4: ccache Hit이 안 됨
원인: __FILE__, __DATE__, __TIME__ 등이 소스에 포함되어 매번 다른 입력으로 인식.
// ❌ ccache 무효화
std::cout << "Built at " << __DATE__ << " " << __TIME__;
해결: 빌드 타임스탬프는 링크 단계에서 주입하거나, 별도 생성 파일로 분리.
// ✅ 버전 정보를 별도 .cpp에서
// version.cpp - 빌드 스크립트가 생성
const char* build_time = "2026-02-28 12:00:00";
문제 5: 모듈 빌드 순서 오류
원인: 모듈 B가 모듈 A를 import하는데, A가 아직 빌드되지 않음.
# ✅ 의존성 순서 보장
add_library(mod_a MODULE a.mpp)
add_library(mod_b MODULE b.mpp)
target_link_libraries(mod_b PRIVATE mod_a) # mod_a 먼저 빌드
문제 6: Unity Build에서 ODR 위반
원인: 여러 cpp를 합치면서 static 변수나 익명 네임스페이스가 중복.
// ❌ foo.cpp
static int counter = 0;
// ❌ bar.cpp (Unity로 합쳐지면)
static int counter = 0; // ODR 위반!
해결: static 대신 익명 네임스페이스, 또는 Unity Build 비활성화.
// ✅
namespace {
int counter = 0;
}
11. 성능 최적화 팁
팁 1: 헤더 정리
// ❌ 불필요한 포함
#include <iostream> // cout만 쓰는데
#include <vector> // 사용 안 함
// ✅ 필요한 것만
#include <ostream> // std::ostream
#include "my_types.h"
forward declaration 활용:
// ✅ 헤더에서
class Bar; // forward declaration
void use_bar(Bar& b); // Bar 정의 불필요
// .cpp에서
#include "bar.h"
void use_bar(Bar& b) { /* ... */ }
팁 2: 템플릿 분리
// ❌ 헤더에 모든 구현
template<typename T>
void process(T x) {
// 긴 구현...
}
// ✅ 선언만 헤더, 구현은 .tpp
// util.hpp
template<typename T> void process(T x);
// util.tpp
#include "util.hpp"
template<typename T>
void process(T x) { /* ... */ }
필요한 타입만 .cpp에서 명시적 인스턴스화:
// util_instances.cpp
#include "util.tpp"
template void process<int>(int);
template void process<double>(double);
팁 3: 병렬 빌드
# make: -j N (N = 병렬 작업 수)
make -j$(nproc)
# CMake
cmake --build build -- -j8
팁 4: 증분 빌드 최대화
- 빌드 디렉토리 고정:
build/한 곳에 유지해 ccache Hit 극대화 - 경로 정규화: ccache
hash_dir = false로 절대 경로 차이 무시 - 타임스탬프 분리: 소스는 Git, 빌드 아티팩트는 별도 저장
팁 5: CI에서 ccache 활용
# GitHub Actions 예시
- uses: actions/cache@v4
with:
path: ~/.ccache
key: ccache-${{ runner.os }}-${{ hashFiles('/CMakeLists.txt', '/*.cpp') }}
restore-keys: ccache-${{ runner.os }}-
- run: |
ccache -s # 캐시 통계
cmake -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -B build
cmake --build build -j4
12. 프로덕션 패턴
패턴 1: 계층별 빌드 전략
flowchart TB
subgraph Layer1["1층: 안정 라이브러리"]
L1[표준 라이브러리]
L2[서드파티]
end
subgraph Layer2["2층: 프로젝트 코어"]
M1[모듈/PCH]
end
subgraph Layer3["3층: 애플리케이션"]
A1[앱 코드]
end
L1 --> L2 --> M1 --> A1
- 1층: PCH 또는 모듈로 한 번만 빌드
- 2층: 변경 적은 코어를 모듈화
- 3층: 자주 바뀌는 앱 코드는 ccache·병렬에 의존
패턴 2: 빌드 프로파일
# CMakePresets.cmake 또는 프로파일
set(CMAKE_BUILD_TYPE "Release")
set(USE_PCH ON)
set(USE_CCACHE ON)
set(CMAKE_CXX_COMPILER_LAUNCHER "ccache")
패턴 3: 빌드 시간 측정
# 시간 측정 (GNU time)
/usr/bin/time -v cmake --build build -j8 2>&1 | grep -E "Elapsed|Maximum resident"
# GCC: -ftime-report
g++ -c -ftime-report -O2 main.cpp 2>&1 | tail -20
성능 비교 표
| 설정 | 클린 빌드 | 증분 빌드 (1파일 수정) |
|---|---|---|
| 기본 (-j1) | 180초 | 45초 |
| -j8 | 35초 | 12초 |
| -j8 + ccache | 35초 (첫) | 2초 |
| -j8 + ccache + PCH | 28초 | 2초 |
구현 체크리스트
-
-j$(nproc)또는CMAKE_BUILD_PARALLEL_LEVEL설정 - ccache 설치 및
CMAKE_CXX_COMPILER_LAUNCHER=ccache적용 - PCH에 자주 쓰는 표준·프로젝트 헤더만 포함 (변경 잦은 헤더 제외)
- PCH 생성 옵션과 TU 컴파일 옵션 일치 확인
- CI에서 ccache 캐시 디렉토리 유지 (
actions/cache등) - 모듈 도입 시 CMake 3.28+ 및 GCC 14+/Clang 18+ 확인
- 빌드 시간 로깅으로 개선 효과 측정
제약사항
constexpr 제약 (C++11)
// ❌ C++11: 제약 많음
constexpr int func(int x) {
// 한 줄 return만 가능
return x * 2;
}
constexpr 확장 (C++14)
// ✅ C++14: 루프, 변수 선언 가능
constexpr int func(int x) {
int result = 0;
for (int i = 0; i < x; ++i) {
result += i;
}
return result;
}
constexpr 확장 (C++20)
// ✅ C++20: 동적 할당, 가상 함수 가능
constexpr int func() {
std::vector<int> vec = {1, 2, 3};
return vec[0] + vec[1] + vec[2];
}
성능 비교
런타임 vs 컴파일 타임
// 런타임 계산
int runtimeFib(int n) {
if (n <= 1) return n;
return runtimeFib(n - 1) + runtimeFib(n - 2);
}
// 컴파일 타임 계산
constexpr int compileFib(int n) {
if (n <= 1) return n;
return compileFib(n - 1) + compileFib(n - 2);
}
int main() {
// 런타임 계산 (느림)
auto start = std::chrono::high_resolution_clock::now();
int r = runtimeFib(30);
auto end = std::chrono::high_resolution_clock::now();
// 약 10ms
// 컴파일 타임 계산 (즉시)
constexpr int c = compileFib(30); // 0ms (이미 계산됨)
std::cout << c << "\n";
}
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ 캐시 최적화 | 메모리 접근 패턴 바꿔서 성능 10배 향상시키기
- C++ 프로파일링 | “어디가 느린지 모르겠어요” perf·gprof로 병목 찾기
- C++ 로깅·Assertion | 프로덕션 간헐적 크래시, 로그 없이 재현 불가일 때
이 글에서 다루는 키워드 (관련 검색어)
C++ 컴파일 타임 최적화, constexpr, 템플릿 메타프로그래밍, PCH, ccache, C++20 모듈, Unity 빌드 등으로 검색하시면 이 글이 도움이 됩니다.
정리
| 항목 | 설명 |
|---|---|
| constexpr | 컴파일/런타임 모두 가능 |
| consteval | 반드시 컴파일 타임 |
| if constexpr | 컴파일 타임 분기 |
| PCH | 헤더 한 번만 파싱, 30~50% 단축 |
| 모듈 | C++20, 의존성 격리 |
| ccache | 컴파일 결과 캐시, 재빌드 가속 |
| Unity 빌드 | 여러 cpp 합쳐 컴파일, 40~60% 단축 |
핵심 원칙:
- 고정 값은 constexpr
- 룩업 테이블은 컴파일 타임
- 타입 분기는 if constexpr
- 성능 크리티컬은 consteval
- 빌드 시간은 PCH·ccache·병렬로 최적화
- 측정으로 효과 검증
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. constexpr로 컴파일 타임에 계산하고, if constexpr로 조건 분기하며, PCH·ccache·모듈로 빌드 시간을 줄이는 방법을 다룹니다. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
한 줄 요약: constexpr·템플릿 메타프로그래밍으로 컴파일 타임에 계산해 런타임 부담을 줄이고, PCH·ccache·모듈로 빌드 시간을 단축할 수 있습니다. 다음으로 GDB/LLDB(#16-1)를 읽어보면 좋습니다.
이전 글: [C++ 실전 가이드 #15-2] 캐시 친화적 코드: 메모리 접근 패턴 최적화
다음 글: [C++ 실전 가이드 #16-1] GDB/LLDB 디버거 완벽 가이드: 버그를 찾는 가장 강력한 도구
관련 글
- C++ 프로파일링 |
- C++ 캐시 최적화 | 메모리 접근 패턴 바꿔서 성능 10배 향상시키기
- C++ 컴파일 시간 최적화 | 모듈·PCH·ccache·분산 빌드 [#51-5]
- C++ STL 알고리즘 기초 | sort·find·count·transform·accumulate 가이드
- C++ Move Semantics | std::move로 불필요한 복사 제거하고 성능 최적화