Skip to content
C++
Domain Track
Difficulty 2/5

Build Time Optimization

C++ build time optimization — precompiled headers, unity builds, modules, forward declarations, include hygiene, ccache, and CMake techniques.

TL;DR

Large C++ projects can take minutes to build. Key levers: precompiled headers (PCH), unity builds, forward declarations, include-what-you-use, ccache, and eventually C++20 modules. Profile the build before optimizing.


Profile the Build First

bash
# CMake Ninja: show build time per file
cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
ninja -j4 -v 2>&1 | ts '%.s' | head -100

# Clang: time each translation unit
clang++ -ftime-trace main.cpp  # generates main.cpp.json
# Open in Chrome DevTools (chrome://tracing) or speedscope.app

Precompiled Headers (PCH)

PCH compiles heavy headers once and reuses the binary:

cmake
# CMake 3.16+ native PCH
target_precompile_headers(myapp PRIVATE
    <vector>
    <string>
    <unordered_map>
    <memory>
    <algorithm>
    "include/common.hpp"
)

# Share PCH across targets (avoids recompiling it for each)
target_precompile_headers(myapp REUSE_FROM mylib)
cpp
// pch.hpp — include expensive headers here
#pragma once
#include <vector>
#include <string>
#include <unordered_map>
#include <memory>
#include <algorithm>
#include <functional>
// Project-wide types
#include "types.hpp"
#include "logging.hpp"

Unity Builds (Jumbo Builds)

Combines multiple .cpp files into one translation unit — reduces redundant parsing:

cmake
# CMake 3.16+ unity builds
set_target_properties(myapp PROPERTIES UNITY_BUILD ON)

# Fine-grained control
set_target_properties(myapp PROPERTIES
    UNITY_BUILD ON
    UNITY_BUILD_BATCH_SIZE 16   # combine up to 16 .cpp files
)

# Exclude files that have ODR or static variable issues
set_source_files_properties(src/globals.cpp PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON)

Warning: Unity builds can break code that relies on internal linkage (static functions, anonymous namespaces with same-named symbols).


Forward Declarations

Remove #include from headers — use forward declarations instead:

cpp
// BAD: widget.hpp including everything
#include <vector>
#include <string>
#include "canvas.hpp"    // heavyweight

// GOOD: forward declare, include in .cpp only
class Canvas;           // forward declaration — no #include needed in header

class Widget {
    Canvas* canvas_;    // pointer: ok with forward declaration
    void draw(Canvas&); // reference param: ok with forward declaration
    // Canvas canvas_; // value member: needs full definition
};

// widget.cpp
#include "widget.hpp"
#include "canvas.hpp"  // full include only where needed

Include What You Use (IWYU)

bash
# Install: sudo apt install iwyu
# Run:
include-what-you-use -std=c++17 file.cpp -- -I include/

# With CMake:
set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "include-what-you-use;-Xiwyu;--no_comments")

IWYU tells you which includes are used vs redundant, reducing unnecessary parsing.


Reducing Template Instantiations

cpp
// Explicit instantiation: instantiate once, use everywhere
// In template.hpp — declare, don't instantiate:
extern template class std::vector<MyType>;

// In template.cpp — force instantiation here only:
template class std::vector<MyType>;

// All other TUs link against this, skip generating the code

ccache (Compiler Cache)

Caches compiled objects — huge win on rebuilds:

bash
# Install
sudo apt install ccache

# CMake integration
cmake -DCMAKE_CXX_COMPILER_LAUNCHER=ccache ..

# Check stats
ccache --show-stats

# Reset
ccache --clear

C++20 Modules (Future)

cpp
// math.cppm — module interface
export module math;

export double add(double a, double b) { return a + b; }
export double square(double x) { return x * x; }

// main.cpp — import module (no header, no repeated parsing)
import math;
import std;  // std library as module (C++23)

int main() {
    std::println("{}", add(1.0, 2.0));
}
cmake
# CMake 3.28+ modules support
target_sources(myapp
    PUBLIC FILE_SET CXX_MODULES FILES
        src/math.cppm
)

Summary: Impact vs Effort

TechniqueBuild speedupEffortNotes
ccache5–20× on rebuildLowInstall + cmake flag
PCH2–5×LowCMake 3.16 built-in
Forward declarations1.5–3×MediumRequires header discipline
IWYU1.5–2×MediumIterative cleanup
Unity builds2–4×LowWatch for ODR issues
C++20 modules5–10×HighToolchain maturity varies