性能优化:减少配置时间、避免冗余扫描、使用Ninja生成器

说实话,很多团队用CMake,项目一大了就头疼——配置时间越来越长,改个文件要等半天。我刚开始带大型项目时也踩过这个坑。后来发现,问题往往不在CMake本身,而是我们没用好它的优化手段。

今天咱们就聊聊怎么让CMake跑得更快。核心就三件事:减少配置时间、避免冗余扫描、换一个更快的生成器。

减少配置时间:别让CMake做重复劳动

配置时间长的根本原因是什么?说白了,就是CMake每次都要重新解析所有CMakeLists.txt,检查依赖,生成构建文件。如果你的项目有几百个模块,这个过程会非常慢。

我个人的习惯是:把不变的部分和变化的部分分开。

核心思路:利用CMake的缓存机制,避免重复检测系统环境。

举个例子,很多人在CMakeLists.txt里这样写:

find_package(OpenCV REQUIRED)
find_package(Boost REQUIRED)
find_package(Python3 REQUIRED)

每次cmake ..都会重新查找这些库。其实,第一次找到后,结果会缓存到CMakeCache.txt里。但如果你不小心改了某些变量,或者删了缓存,就得重新来一遍。

我建议的做法是

  • find_package放到一个单独的cmake/FindModules.cmake文件中
  • include_guard()防止重复加载
  • 设置CMAKE_FIND_NO_INSTALL_PREFIX等变量,减少搜索路径

另外,project()命令也会触发一些系统检测。如果你只是改了一个源文件,完全没必要重新执行project()。嗯,这里要注意:project()只在第一次配置时执行,后续cmake --build不会触发它。

小技巧:用cmake --build . --target help查看可用的目标,比重新配置快得多。

避免冗余扫描:管好你的文件搜索

冗余扫描是配置时间的第二大杀手。你想想看,每次file(GLOB ...)都要遍历整个目录树,如果项目里有几千个文件,那得多慢。

我曾经接手过一个项目,配置时间长达3分钟。一查,发现CMakeLists.txt里用了大量的file(GLOB_RECURSE ...)。每次配置都要扫描整个src/目录,包括build/目录本身——这就形成了一个循环扫描!

警告file(GLOB)file(GLOB_RECURSE)不会自动检测新增文件。如果你添加了新源文件,必须重新运行cmake。这就是为什么官方文档建议显式列出源文件。

那怎么办?我的经验是:

  1. 显式列出源文件:虽然写起来麻烦,但配置速度快,而且不会漏文件
  2. 如果非要用GLOB:加上CONFIGURE_DEPENDS标志,让CMake在构建时重新检查
  3. 避免递归扫描:用file(GLOB ...)代替file(GLOB_RECURSE ...),只扫描当前目录

来看一个对比:

# 慢的做法
file(GLOB_RECURSE SOURCES src/*.cpp)

# 快的做法
set(SOURCES
    src/main.cpp
    src/utils.cpp
    src/core/engine.cpp
)

显式列出虽然多写几行,但配置时间从几十秒降到了几秒。我个人觉得,这笔账很划算。

使用Ninja生成器:让构建飞起来

好了,配置时间优化了,文件扫描也避免了。接下来就是构建本身的速度了。

默认情况下,CMake生成的是Makefile。Makefile是串行执行的,虽然可以加-j参数并行,但它的依赖解析效率不高。

Ninja是什么? 它是一个轻量级的构建系统,专为速度而生。它的设计哲学就是「快」——启动快、解析快、执行快。

我做过一个测试:同样的项目,用Makefile构建需要45秒,用Ninja只需要12秒。差距就是这么明显。

为什么Ninja更快?

  • 没有复杂的语法解析,Ninja文件是扁平的
  • 依赖图是显式的,不需要像Make那样推导
  • 增量构建时,只重新编译真正改变的文件

使用Ninja很简单,只需要在配置时指定生成器:

cmake -G Ninja ..
cmake --build .

注意,你需要先安装Ninja。在Ubuntu上:sudo apt install ninja-build。在macOS上:brew install ninja

我建议所有新项目都默认使用Ninja。除非你有特殊需求(比如必须用Visual Studio的MSBuild),否则Ninja是性能最优的选择。

下面这张图展示了CMake配置和构建的完整流程,以及Ninja在其中的位置:

CMake构建流程与性能优化 CMake 配置 解析 CMakeLists.txt 生成构建文件 Makefile / Ninja 构建 编译 + 链接 性能优化关键点 减少配置时间 缓存系统检测结果 避免冗余扫描 显式列出源文件 使用 Ninja 更快的构建速度 配置时间优化 → 减少重复检测 构建时间优化 → 使用 Ninja 生成器

综合实战:一个优化后的CMakeLists.txt

说了这么多,咱们来看一个实际例子。这是我之前优化过的一个项目结构:

# CMakeLists.txt (顶层)
cmake_minimum_required(VERSION 3.20)
project(MyApp LANGUAGES CXX)

# 设置Ninja推荐选项
if(CMAKE_GENERATOR MATCHES "Ninja")
  set(CMAKE_COLOR_DIAGNOSTICS ON)
endif()

# 避免冗余扫描:显式添加子目录
add_subdirectory(src)
add_subdirectory(libs)
add_subdirectory(tests)

# 缓存系统检测结果
set(CMAKE_FIND_NO_INSTALL_PREFIX TRUE CACHE BOOL "" FORCE)
# src/CMakeLists.txt
# 显式列出源文件,不用GLOB
set(SOURCES
    main.cpp
    app.cpp
    config.cpp
    network/http_client.cpp
    network/websocket.cpp
)

add_executable(myapp ${SOURCES})
target_link_libraries(myapp PRIVATE core_lib)

你看,这里没有用file(GLOB),没有递归扫描,而且显式指定了生成器选项。配置时间从原来的2分15秒降到了8秒。

补充一点:如果你用的是CMake 3.20以上版本,可以试试CMAKE_COLOR_DIAGNOSTICS,Ninja支持彩色输出,错误信息一目了然。

避坑指南

我曾经犯过一个错误:在CI脚本里每次都执行cmake -G Ninja ..,但忘了安装Ninja。结果CI报错说找不到生成器。后来我加了一个检测:

if(NOT CMAKE_GENERATOR)
  set(CMAKE_GENERATOR "Ninja" CACHE STRING "Build system" FORCE)
endif()

这样,如果系统没有Ninja,CMake会回退到默认生成器,至少不会直接崩溃。

另外,Ninja在Windows上需要额外配置。如果你用Visual Studio,Ninja也能用,但需要安装ninja.exe并加入PATH。我个人在Windows上更推荐Ninja + Clang的组合,速度比MSBuild快不少。

好了,关于性能优化就聊这么多。记住三个关键词:缓存、显式、Ninja。下次配置项目时,试试这些方法,你会感受到明显的差别。


公众号:蓝海资料掘金营,微信deep3321