Ninja比Make并发更稳,因其采用静态构建图与动态负载感知调度,运行时不需重算依赖;而Make每次都要展开变量、执行shell函数,-j越大锁竞争越严重,导致CPU利用率波动大。

Ninja 默认就满载并发,不需要额外配置“开启并发”——它启动时自动用 ninja -j 等效模式跑满物理核(甚至超线程),关键在于别让它被拖慢。
为什么 ninja 比 make -j 并发更稳
Ninja 的调度器是静态构建图 + 动态负载感知的组合:它在解析 build.ninja 时就已确定所有依赖边,运行时不需反复重算;而 GNU Make 每次都要重新展开变量、执行 shell 函数、匹配隐式规则,-j 越大,竞争和锁开销越明显。实测中,16 核机器上 ninja 的 CPU 利用率曲线平滑,make -j16 常有 20%+ 时间卡在规则解析而非编译。
- Make 的
-j是粗粒度并行,Ninja 的-j是细粒度任务级并行 - Ninja 不做任何 shell 调用(除非你显式写在
command里),避免 fork 开销 - 它默认启用
restat机制,每次只 re-run 真正输出变更的命令,减少无效并发
ninja -j 参数怎么设才不翻车
多数人直接信“越多越好”,结果内存爆掉或磁盘 IO 成瓶颈。真实项目里,推荐按以下逻辑调:
- 纯 CPU 密集型(如模板展开多的 C++):
ninja -j$(nproc)(Linux)或ninja -j$(sysctl -n hw.ncpu)(macOS),即物理核心数 - 含大量链接步骤(
ld占内存):ninja -j$(($(nproc)/2+1)),防止 OOM - SSD 但内存小(ninja -j4 起步,观察
htop中 swap 使用率 - CI 环境(Docker/runner):
ninja -j2更稳妥,避免因资源限制触发超时
注意:ninja 不会读取 MAKEFLAGS,所以 export MAKEFLAGS=-j8 对它完全无效。
立即学习“C++免费学习笔记(深入)”;
CMake 生成 Ninja 文件时埋下的并发隐患
很多人以为只要 cmake -G Ninja 就万事大吉,其实 CMakeLists.txt 里几个写法会悄悄破坏 Ninja 的并发效率:
-
add_custom_target未声明DEPENDS或用了BYPRODUCTS但没配VERBATIM→ Ninja 无法判断是否可并行,降级为串行 -
execute_process()在configure_file()或find_package()里被调用 → 这类命令在 CMake configure 阶段就执行了,和 Ninja 并发无关,但会让cmake本身变慢,拖累整个流程启动 - 用
file(GLOB_RECURSE SOURCES ...)收集源码 → 如果头文件变动没触发cmake重运行,Ninja 就不知道新 .h 被哪些 .cpp 包含,导致增量构建漏编译(看似并发高,实则结果错)
正确做法是显式列出源文件,或用 target_sources() + target_include_directories() 让 CMake 自动推导依赖。
真正卡并发的不是 Ninja,而是编译器和链接器
Ninja 再快,也得等 clang++ 或 g++ 返回。常见瓶颈点:
- 单个
.o编译耗时 >5s:检查是否启用了-O3 -flto=thin这类重型优化,改用-O2开发期构建 - 链接阶段卡住(
ld占 CPU 100% 但进度不动):启用-Wl,--threads(Gold linker)或换mold(比ld快 3–5 倍) - ccache 命中率 CMAKE_CXX_COMPILER_LAUNCHER 指向的是
ccache而非distcc,且ccache -s显示 stats 正常增长
并发提升的上限,永远由最慢的那个环节决定——Ninja 只是那个从不拖后腿的调度员。


















