性能优化:并行下载、增量更新、缓存策略、减少网络请求
说实话,刚开始用 FetchContent 的时候,我根本没想过性能这回事。项目小嘛,几个依赖一拉就完事了。直到有一次,团队的项目膨胀到十几个依赖,每次 cmake --build 都要等上五六分钟去下载源码……嗯,那感觉就像在等老电脑开机。
后来我花了不少时间研究怎么让 FetchContent 跑得更快。说白了,就四个方向:并行下载、增量更新、缓存策略、减少网络请求。今天我把这些经验整理出来,希望能帮你少走弯路。
并行下载:别让依赖排队等
FetchContent 默认是串行下载的。一个依赖下载完,才开始下一个。你想想看,如果三个依赖分别需要 10 秒、8 秒、12 秒,串行就是 30 秒。但如果并行呢?理论上只需要 12 秒。
怎么开启并行?其实很简单,设置 FETCHCONTENT_QUIET 和 CMAKE_BUILD_PARALLEL_LEVEL 就行。但我个人习惯用另一种方式——直接修改 CMake 的下载命令。
# 在调用 FetchContent 之前设置
set(FETCHCONTENT_QUIET OFF)
set(CMAKE_BUILD_PARALLEL_LEVEL 4)
# 或者更直接:修改下载命令模板
set(CMAKE_GET_DOWNLOAD_COMMAND
"${CMAKE_COMMAND} -P \"${CMAKE_CURRENT_BINARY_DIR}/download.cmake\" --parallel 4"
)
增量更新:只下载变化的部分
增量更新这个功能,说实话,CMake 原生支持得不太好。FetchContent 每次都会重新下载整个仓库。但我们可以用一些小技巧来模拟增量更新。
我记得有一次,项目依赖了一个频繁更新的库,每次改一行代码就要重新下载整个 50MB 的仓库。后来我改用 Git 子模块 + 本地缓存的方式,才解决了这个问题。
这里分享一个我常用的方案:
# 先检查本地缓存
if(EXISTS "${CMAKE_SOURCE_DIR}/.cache/${dep_NAME}")
message(STATUS "使用本地缓存: ${dep_NAME}")
FetchContent_Declare(
${dep_NAME}
SOURCE_DIR "${CMAKE_SOURCE_DIR}/.cache/${dep_NAME}"
)
else()
# 正常下载
FetchContent_Declare(
${dep_NAME}
GIT_REPOSITORY https://github.com/xxx/xxx.git
GIT_TAG v1.0.0
)
endif()
--clean-cache 选项。
缓存策略:把下载过的依赖存起来
缓存是性能优化的重头戏。FetchContent 本身有缓存机制,但默认只缓存到构建目录。一旦你删了 build 文件夹,一切都要重新下载。
我建议把缓存放到一个全局位置,比如 ~/.cmake_cache。这样多个项目可以共享缓存,而且不会因为清理构建目录而丢失。
# 在 ~/.cmake/init.cmake 或 CMakeLists.txt 开头设置
set(ENV{FETCHCONTENT_BASE_DIR} "$ENV{HOME}/.cmake_cache/fetchcontent")
# 或者指定本地缓存路径
set(FETCHCONTENT_BASE_DIR "${CMAKE_SOURCE_DIR}/.cache/fetchcontent")
这样做的好处很明显:
- 第一次下载后,后续构建直接使用缓存
- 多个项目共用同一个依赖时,只需下载一次
- 即使删除 build 目录,缓存依然存在
减少网络请求:能省则省
网络请求是性能瓶颈的重灾区。每次请求都有延迟,哪怕只是检查版本更新。我见过有些项目每次构建都去 GitHub 检查 tag 列表,白白浪费好几秒。
减少网络请求的几个实用技巧:
- 固定版本号:不要用
main或latest,指定具体的 tag 或 commit hash。 - 使用本地镜像:如果公司有 Git 镜像,优先用内网地址。
- 关闭更新检查:设置
FETCHCONTENT_UPDATES_DISCONNECTED为ON。
# 关闭所有更新检查
set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
# 或者针对单个依赖关闭
FetchContent_Declare(
my_dep
GIT_REPOSITORY https://github.com/xxx/xxx.git
GIT_TAG v1.2.3
GIT_SHALLOW TRUE # 只拉取最新 commit,减少数据量
GIT_PROGRESS TRUE
)
GIT_SHALLOW 设为 TRUE,结果发现有些依赖需要完整的 git 历史来做版本比较。所以,如果你的依赖需要 git describe 之类的操作,别用 shallow clone。
整体优化流程图
下面这张图总结了 FetchContent 性能优化的整体流程。从下载到缓存,每一步都有对应的优化策略。
综合配置示例
最后,我把上面所有的优化策略整合到一个完整的配置里。你可以直接复制到项目中使用。
# 性能优化配置
# 放在 CMakeLists.txt 最前面
# 1. 缓存策略:使用项目级缓存
set(FETCHCONTENT_BASE_DIR "${CMAKE_SOURCE_DIR}/.cache/fetchcontent")
# 2. 减少网络请求:关闭更新检查
set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
# 3. 并行下载:设置并行数
set(CMAKE_BUILD_PARALLEL_LEVEL 4)
# 4. 使用 shallow clone 减少数据量
set(FETCHCONTENT_QUIET OFF)
# 声明依赖时指定具体版本
FetchContent_Declare(
json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.2
GIT_SHALLOW TRUE
)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.1.0
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(json fmt)
FETCHCONTENT_BASE_DIR 设置好,就能看到立竿见影的效果。然后再逐步加上并行下载和增量更新。至于减少网络请求,那是锦上添花的事,但做了一定不亏。
嗯,这些就是我这些年折腾 FetchContent 性能优化的一些心得。希望能帮你把构建时间降下来,把摸鱼时间提上去。
公众号:蓝海资料掘金营,微信deep3321