Beyond CMAKE_UNITY_BUILD_BATCH_SIZE: Alternative Strategies for Faster CMake Builds


What it is

  • It sets the default maximum number of source files that can be combined into a single "unity source file" during a unity build.
  • CMAKE_UNITY_BUILD_BATCH_SIZE is a CMake variable introduced in version 3.16.

Unity Builds in CMake

  • This reduces the compiler overhead associated with processing individual files.
  • It works by combining multiple source files (typically translation units) into a single source file before compilation.
  • Unity builds are an optimization technique in CMake that can improve compilation times for large projects.

CMAKE_UNITY_BUILD_BATCH_SIZE in Action

    • You can set CMAKE_UNITY_BUILD_BATCH_SIZE to a specific value during your CMake configuration process.
    • This value becomes the initial limit for all targets that have unity builds enabled (using the UNITY_BUILD target property).
  1. Unity Build Creation

    • When a target with unity builds enabled is created, CMake considers the value of CMAKE_UNITY_BUILD_BATCH_SIZE.
    • It distributes the original source files for that target across as many unity source files as needed to stay within the batch size limit.

Example

set(CMAKE_UNITY_BUILD_BATCH_SIZE 100)  # Set the default limit to 100 source files per unity source

target_compile_definitions(my_target PRIVATE UNITY_BUILD)

In this example:

  • The maximum number of source files in any unity source file for my_target will be 100 (unless overridden for my_target specifically).

Key Points

  • Choosing the optimal batch size depends on various factors like project size, compiler behavior, and hardware characteristics. Experimentation might be necessary to find the best value for your project.
  • You can adjust the batch size for specific targets using the UNITY_BUILD_BATCH_SIZE target property.
  • CMAKE_UNITY_BUILD_BATCH_SIZE is a global variable affecting all targets with unity builds enabled by default.

Additional Considerations

  • Consider other optimization techniques like precompiled headers if unity builds don't provide significant gains.
  • Unity builds can be beneficial for projects with many small source files, but might not be as effective for projects with large or complex files.


Setting a Global Default Batch Size

# Set the global default batch size to 50 files
set(CMAKE_UNITY_BUILD_BATCH_SIZE 50)

# All targets with `UNITY_BUILD` enabled will inherit this limit unless overridden.
target_compile_definitions(my_target1 PRIVATE UNITY_BUILD)
target_compile_definitions(my_target2 PRIVATE UNITY_BUILD)

Overriding the Default for a Specific Target

# Global default is still 50 files
set(CMAKE_UNITY_BUILD_BATCH_SIZE 50)

# Set a lower batch size of 20 files for `my_target3` only
target_compile_definitions(my_target3 PRIVATE UNITY_BUILD)
set_target_properties(my_target3 PROPERTIES UNITY_BUILD_BATCH_SIZE 20)

Disabling Unity Builds for a Target

# Global default is still 50 files
set(CMAKE_UNITY_BUILD_BATCH_SIZE 50)

# Target `my_target4` will not use unity builds, regardless of the global setting
target_compile_definitions(my_target4 PRIVATE UNITY_DISABLE_BUILD)
# Check if the current project supports unity builds
if(CMAKE_UNITY_BUILD)
  message(STATUS "Unity builds are enabled with a default batch size of ${CMAKE_UNITY_BUILD_BATCH_SIZE}")
else()
  message(STATUS "Unity builds are not enabled")
endif()


Precompiled Headers (PCH)

  • PCHs are source files containing precompiled declarations of frequently used headers. Compiling these declarations once saves time by avoiding repetitive compilation in each translation unit.
    • Use the PRECOMPILED_HEADER target property to configure PCH.

Parallel Compilation

  • Modern compilers often support parallel compilation to utilize multiple CPU cores or threads.
    • Use compiler flags like -j or -fopenmp to enable this (consult your compiler documentation for specific options).

Out-of-Source Builds

  • An out-of-source build keeps your source code separate from the build directory. This avoids unnecessary recompilation of source files when only CMakeLists.txt changes.
    • Use the CMAKE_CURRENT_BINARY_DIR variable to reference the build directory within your CMake scripts.

Caching Build Results

  • Some build systems like Ninja (often used with CMake) can cache compilation results, reducing recompilation when source files haven't changed.
    • Consider using Ninja as your build system if it's not already the default.

Refactoring Code

  • Code with better modularity and reduced dependencies can lead to faster compilations. Break down large source files into smaller, more focused units.

Choosing the Right Approach

  • Refactoring Code
    Improves build speed and code maintainability in the long run.
  • Caching Build Results
    Reduces redundant builds when source files haven't changed.
  • Out-of-Source Builds
    Improves build speed by avoiding unnecessary recompilation of unchanged source files.
  • Parallel Compilation
    Take advantage of multi-core architectures if your build process is CPU-bound.
  • Precompiled Headers
    Useful when many translation units include the same headers repeatedly.
  • Unity Builds
    Good for projects with many small source files, but might not be effective for large files or complex code.
  • It's often beneficial to experiment with different approaches to find the best combination for your project. Analyze build times to identify bottlenecks and target them with appropriate optimization techniques.