吐槽一下我们底层核心通信库的编译速度。项目本身有几十万行 C++ 代码,每次只要动一个底层的公共结构体,整个增量编译就直接失效,CI/CD 跑一次要足足等十五分钟,简直就是带薪摸鱼的绝佳借口。但天天这么折腾,研发效率实在顶不住。为了解决编译耗时过长的大坑,我们对 CMake 构建脚本进行了全面重构,主要通过引入 ccache 编译缓存和预编译头文件(Precompiled Headers, PCH)这两个大招来完成极限加速。

首先是 ccache,它的核心原理非常直接:拦截编译器命令,把编译好的 .o 目标文件缓存起来。只要源文件内容、编译选项和编译器版本没变,就直接拿缓存拼装,根本不走真正的编译流程。这对于 CI/CD 机器上频繁的 clean build 简直是救命稻草。

在 CMake 中配置 ccache 其实非常简单,不需要在系统里搞各种软链接污染环境变量。现代 CMake(3.4+)提供了 CMAKE_CXX_COMPILER_LAUNCHER 变量,我们可以直接在 CMakeLists.txt 里声明。

接着是预编译头文件。在 C++ 里,每次 #include 一个头文件,编译器都会把它展开并解析一遍。如果每个 .cpp 都引入了 <vector><string><unordered_map> 以及像 boostnlohmann/json 这种超级庞大的模板库,编译器的前端解析开销会非常恐怖。通过预编译头文件,编译器只会编译一次这些不经常变动的头文件,并保存为一个二进制状态,后续编译其他源文件时直接秒读。

下面是我们重构后的核心 CMakeLists.txt 配置片段:

cmake_minimum_required(VERSION 3.16)
project(EngineCore CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 自动寻找并配置 ccache
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
    message(STATUS "Found ccache: ${CCACHE_PROGRAM}")
    # 设置 C 和 CXX 的编译器启动器
    set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
    set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_PROGRAM})
else()
    message(WARNING "ccache not found! Build times will not be cached.")
endif()

# 核心动态库目标
add_library(EngineCore SHARED
    src/core.cpp
    src/network.cpp
    src/protocol.cpp
    src/serializer.cpp
    src/utils.cpp
)

target_include_directories(EngineCore PUBLIC
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

# 开启预编译头文件加速(CMake 3.16+ 官方原生支持)
# 我们把不常改动的标准库和第三方库头文件塞进去
target_precompile_headers(EngineCore PRIVATE
    <iostream>
    <vector>
    <string>
    <memory>
    <unordered_map>
    <algorithm>
    <functional>
    <thread>
    <chrono>
    "include/thirdparty/nlohmann/json.hpp"
)

# 如果是外部目标要使用我们,也可以指定预编译头文件的传递行为
# 这里演示如何单独为某个文件取消 PCH(例如某些会产生冲突的遗留代码)
set_source_files_properties(src/legacy_code.cpp PROPERTIES
    SKIP_PRECOMPILE_HEADERS ON
)

引入这套配置后,本地二次编译几乎是秒开。CI 系统里通过将 ~/.ccache 目录加入缓存列表,冷启动编译时间从 14 分钟骤降到 2.5 分钟左右,编译效率提升了将近 5 倍,心智负担小了太多。

不过使用 PCH 也有个潜在的弊端:它会隐式地往你的源文件里注入那些预编译的头文件。如果哪天你想把某个 .cpp 拆到另一个没有配置 PCH 的子模块里,会突然暴出一堆 undefined 错误,因为你可能漏写了基础头文件的 #include。所以,建议团队成员即使在享受 PCH 加速时,依然在源文件里老老实实写上自己所需的 #include

大家在大型 C++ 项目中,还有没有配合使用过其他更激进的编译优化手段,比如 Distcc 分布式编译或者 Gold/LLD 链接器优化?效果如何?