加速构建

本节内容将讲述如何加速 C++ 项目的构建

划分子项目

将大型项目划分为各种粒度更小的目标可以在其他目标的代码没有更改时明显降低编译时间,CMake 只会编译代码更改过的目标。善用子项目和目标!

预编译头

在 CMake 3.16 之后,其添加了对预编译头的支持 1

例如:

target_precompile_headers(myTarget
PUBLIC
    project_header.h
PRIVATE
    [["other_header.h"]]
)

该指令会让 CMake 生成预编译头文件(cmake_pch.h)和预编译文件(.pch, .gch 或 .pchi)

备注

  • vc++ 生成的是 .pch,而 gcc 生成的是 .gch

1

target_precompile_headers

Unity Build

Unity Build 有称为单元构建或者大型构建(Jumbo Build)。由于历史遗留问题,C++ 编译单元是是以文件为单位的,而不是以模块为单位的。这中情况直接导致了以下问题:

  • 如果 N 个源文件引用到了同一个头文件,则这个头文件需要解析 N 次

  • 如果头文件中有模板(STL/Boost),则该模板在每个 cpp 文件中使用时都会做一次实例化,N个源文件中的std::vector<int>会实例化N次。

为了解决这个问题,CMake 引入了 Unity Build,其工作机制相当于将多个文件合并到一个单独的文件中,该文件将会包含所有 源代码文件

CMake 通过变量 CMAKE_UNITY_BUILD 来控制是否开启单元构建。如果开启,CMake 将会先把所有文件 拷贝 到一个文件,然后对此单个文件进行编译

set(CMAKE_UNITY_BUILD ON)

开启此开关后,再进行一次构建,你会发现你的 CMakeFiles 目录下(或子目录下)生成了 Unity/unity_****.cxx 文件

备注

  • 当你打开 CMake 生成的文件后,你会发现 CMake 只是生成了一个 #include 需要的头文件,而不是将头文件机械地进行复制

  • CMake 的 Unity Build 是以一个 CMake 项目为单位的,或者是 CMake “模块”

如果直接开单元构建,不同文件中的宏可能会发生冲突,这时可以手动指定哪些文件应当被组合在一起:

add_library(example_library
            source1.cxx
            source2.cxx
            source3.cxx
            source4.cxx)

set_target_properties(example_library PROPERTIES
                     UNITY_BUILD_MODE GROUP
                     )

set_source_files_properties(source1.cxx source2.cxx source3.cxx
                           PROPERTIES UNITY_GROUP "bucket1"
                           )
set_source_files_properties(source4.cxx
                           PROPERTIES UNITY_GROUP "bucket2"
                           )

但是由于开发环境的原因,可能有些机器不适合使用单元构建,因此不建议在 CMakeLists 中将 CMAKE_UNITY_BUILD 设置为真,此选项应当在构建时选定

Ccache

Ccache 即编译器缓存。通过缓存编译器以前的编译来加速编译。Ccache 是经过严格编写的。能确保至少在编译结果的正确性上和直接使用make所得到的结果完全一致。甚至不同工程使用的 Ccache 也能保证其正确性。2

备注

Ccache 的性能依赖于缓存所在的介质的速度和缓存的命中率。所以将缓存的位置设置在 tmpfs 上能够加快编译速度

以下情况可从 Ccache 中获益:

  • 执行了 make clean 的项目的编译

  • 同一源码在不同项目中被编译

  • 用户间共享缓存以加速构建。这在共享编译服务器非常游泳

Ccache 亦有其限制:

  • 不支持非单文件(例如多文件的编译和链接)的编译

  • 若传入的编译器标志不支持则回退到实际的编译器

  • 若缓存命中率过低,则会带来额外开销 3

Ccache 的使用非常简单:

  1. 安装 Ccache

  2. 使用 Ccache 替换编译器

    export CC="Ccache clang"
    export CPP="Ccache clang++"
    export CXX="Ccache clang++"
    

需要注意的是,如果你 混用了 ccahe 和预编译头,需要添加额外的信息:4

  • 对于 GCC 来说,添加编译时选项:-fpch-preprocess

  • 对于 Clang 来说,添加编译时选项:-include-pch

2

Ccache提高编译速度的具体使用

3

Ccache: 性能

4

CCache 官网

5

CCache:预编译头

并行编译

在 CMake 生成文件后,对 Makefile 可以通过 make -j8 来开启 8 线程并行编译

对于 IDE 而言,可以在 构建选项 中添加 -j8

Ninja

Ninja 是一个 Make 的替代品,专注于速度。你可以使用 CMake -G 切换生成器。 Ninja 默认使用多线程编译

cotire

cotire 是用于加速 CMake 编译的一个工具,其主要功能是添加 预编译头unity build,而这些功能在 CMake 3.16 之后已经内置了。

cotire

分布式编译

Distcc

Distcc 与 Ccache 混用需要注意:Using Ccache with other compiler wrappers