29、性能优化:通过 Presets 配置并行构建、ccache、LTO 等
说实话,很多团队的项目越做越大,编译一次能喝三杯咖啡。我见过不少开发者,每天把大量时间浪费在等编译上。其实,CMake Presets 里藏着不少提速的妙招。今天我们就聊聊怎么通过 Presets 把并行构建、ccache 缓存、LTO 链接优化这些东西安排得明明白白。
并行构建:别让 CPU 闲着
并行构建,说白了就是让电脑多核同时干活。你想想看,现在谁电脑不是 8 核 16 核的?只用一个核编译,那简直是暴殄天物。
在 Presets 里,我们可以通过 jobs 字段来控制并行度。我个人习惯设成 0,意思是让 Ninja 或 Make 自己判断最优并行数。但有些场景下,比如你在服务器上编译,同时跑着好几个任务,那就得手动限制一下。
{
"configurePresets": [
{
"name": "default",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
],
"buildPresets": [
{
"name": "release",
"configurePreset": "default",
"jobs": 0
},
{
"name": "release-limited",
"configurePreset": "default",
"jobs": 4
}
]
}
ccache:让重复编译见鬼去
ccache 是个好东西。它会把编译过的 .o 文件缓存起来。下次再编译时,如果源文件没变,就直接从缓存里拿结果。嗯,这里要注意:ccache 不是万能的,它只对 C/C++/Objective-C 有效。
在 Presets 里启用 ccache 很简单,设置 CMAKE_C_COMPILER_LAUNCHER 和 CMAKE_CXX_COMPILER_LAUNCHER 就行。
{
"configurePresets": [
{
"name": "with-ccache",
"inherits": "default",
"cacheVariables": {
"CMAKE_C_COMPILER_LAUNCHER": "ccache",
"CMAKE_CXX_COMPILER_LAUNCHER": "ccache"
}
}
]
}
/usr/bin/gcc,有时候用 /usr/local/bin/gcc,ccache 会认为这是不同的编译器,缓存就失效了。解决办法:统一编译器路径,或者在 ccache 配置里设置 CCACHE_NOHASHDIR=true。
你还可以通过环境变量控制 ccache 的行为。比如设置缓存大小、日志级别等。我个人习惯把缓存上限设成 5GB,太小了容易频繁淘汰,太大了浪费磁盘。
{
"configurePresets": [
{
"name": "with-ccache-tuned",
"inherits": "default",
"environment": {
"CCACHE_MAXSIZE": "5G",
"CCACHE_LOGFILE": "${sourceDir}/ccache.log"
},
"cacheVariables": {
"CMAKE_C_COMPILER_LAUNCHER": "ccache",
"CMAKE_CXX_COMPILER_LAUNCHER": "ccache"
}
}
]
}
LTO:链接时优化,让代码跑得更快
LTO(Link Time Optimization)是个好东西,但也是个双刃剑。它能在链接阶段做全局优化,比如内联跨编译单元的函数、消除死代码。代价是链接时间会变长,内存占用也会增加。
在 Presets 里启用 LTO,主要靠 CMAKE_INTERPROCEDURAL_OPTIMIZATION 这个变量。不同编译器对这个变量的支持程度不一样:
| 编译器 | CMAKE_INTERPROCEDURAL_OPTIMIZATION | 实际效果 |
|---|---|---|
| GCC | ON | 启用 -flto |
| Clang | ON | 启用 -flto=thin 或 -flto=full |
| MSVC | ON | 启用 /GL 和 /LTCG |
{
"configurePresets": [
{
"name": "release-lto",
"inherits": "default",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_INTERPROCEDURAL_OPTIMIZATION": "ON"
}
}
]
}
把这些组合起来:一个完整的性能优化 Preset
好了,我们把并行构建、ccache、LTO 都揉到一起,做一个完整的 Preset。我个人习惯把这种 Preset 命名为 release-optimized,一看就知道是干啥的。
{
"configurePresets": [
{
"name": "release-optimized",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_C_COMPILER_LAUNCHER": "ccache",
"CMAKE_CXX_COMPILER_LAUNCHER": "ccache",
"CMAKE_INTERPROCEDURAL_OPTIMIZATION": "ON"
},
"environment": {
"CCACHE_MAXSIZE": "5G",
"CCACHE_LOGFILE": "${sourceDir}/ccache.log"
}
}
],
"buildPresets": [
{
"name": "release-optimized",
"configurePreset": "release-optimized",
"jobs": 0
}
]
}
使用的时候,一行命令搞定:
cmake --preset release-optimized
cmake --build --preset release-optimized
知识体系一览
下面这张图帮你理清本章的核心逻辑:
一些额外的建议
最后,分享几个我在实战中踩过的坑:
- ccache 和 LTO 的兼容性:ccache 4.0 以下版本对 LTO 支持不好,缓存命中率会下降。升级到 4.0 以上基本没问题。
- 并行数不是越大越好:我见过有人把 jobs 设成 64,结果内存爆了,编译直接 OOM。建议根据机器内存来,一般每个编译任务需要 1-2GB 内存。
- LTO 的链接时间:如果你项目特别大,LTO 链接可能会很慢。可以考虑用 ThinLTO(Clang 支持),它比 Full LTO 快不少,优化效果也接近。
- 团队共享:把这些 Preset 提交到 Git 仓库里,团队成员直接
cmake --preset release-optimized就能用,不用每个人手动配环境变量。
CMakePresets.json,里面包含 debug、release、release-optimized 三个 Preset。debug 不开优化,release 开基本优化,release-optimized 才开 LTO 和 ccache。这样不同场景各取所需。
好了,关于性能优化就聊这么多。记住一句话:编译速度是开发效率的一部分,花点时间配置好 Presets,长期来看绝对值得。
公众号:蓝海资料掘金营,微信deep3321