1、make 不够快吗?
用了几年 make 后,你有没有发现一个现象:
make -j8
你开了 8 个并行线程,CPU 跑满了,但 make 还是要在启动时「卡」几秒钟才真正开始编译。
这几秒钟里,make 在干什么?
它在构建依赖图——从 Makefile 里解析谁依赖谁、谁先编译谁后编译。Makefile 是文本格式,make 每次启动都要从头解析一遍。项目越大,这个解析时间越长。
而且 make 对依赖图的推理也偏保守——即使 A 和 B 之间没有先后依赖,make 也常常把它们串行执行,因为没有「全局视野」。
Ninja 就是冲这两个痛点来的:更快的启动、更激进的并行。
2、Ninja 的设计哲学
Ninja 的 Slogan 很实在:「致力于让你编译得更快」。
它的设计思路很简单:
- 构建文件不是给人写的,是给程序生成的。Ninja 的
.ninja文件格式极其精简,几乎没有语法糖 - 代码量很小,解析快到几乎可以忽略。你用 make 时那几秒钟的「发呆」,在 Ninja 里变成了毫秒级
- 并行判断更精准,能最大程度利用多核 CPU
Ninja 的定位不是替代 CMake,而是替代 make。你可以把 Ninja 理解为「让构建启动更快、执行更快」的后端引擎。
CMakeLists.txt → cmake -G Ninja → build.ninja → ninja 执行
3、用 CMake 生成 Ninja 构建文件
几乎不需要额外学习。你只需要在 cmake 时换个生成器:
mkdir build && cd build
cmake .. -G Ninja
ninja
和 cmake .. && make 完全一样的使用方式。CMakeLists.txt 一行都不用改。
如果你还没装 Ninja:
# Ubuntu / Debian
sudo apt install ninja-build
# macOS
brew install ninja
跑起来之后你会立刻感受到区别——Ninja 几乎没有任何停滞,直接就开始编译了。
4、build.ninja 长什么样
虽然你一般不需要直接写 .ninja 文件,但看一眼它的结构有助于理解为什么它这么快:
# 变量
cflags = -Wall -g
# 规则(rule):定义一种「如何编译」
rule cc
command = gcc $cflags -c $in -o $out
description = CC $out
# 构建(build):描述「用什么规则、从什么编出什么」
build main.o: cc main.c
build utils.o: cc utils.c
# 链接规则
rule link
command = gcc $in -o $out
description = LINK $out
build myapp: link main.o utils.o
# 默认目标
default myapp
规则(rule)定义了编译器命令模板,构建(build)声明了具体的编译任务和依赖关系。格式极度精简,没有任何花哨的东西——所以解析极快。
5、Ninja vs Make:速度对比
以一个中等规模的 C 项目(约 500 个源文件)为例,纯增量编译(什么都不改):
# make
time make -j8
# real 0m3.2s
# ninja
time ninja
# real 0m0.3s
Ninja 的增量编译(no-op build)比 make 快了近 10 倍。这 3 秒的差异,在你频繁改代码-编译-测试的循环里累积起来,一天能省很多时间。
全量编译的速度差距没那么大,但 Ninja 在并行调度上的优势仍然能让它略快一些。如果使用 ccache 等缓存工具,Ninja 的 no-op 速度优势会更明显。
6、Ninja 的局限性
Ninja 虽然快,但不是万能的:
- 不适合手工编写:
.ninja文件极其枯燥。你永远不会想手写它 - 需要搭配高级构建系统:Ninja 只是一个执行引擎,你仍然需要 CMake(或 GN、Meson)来生成构建文件
- 没有条件判断、函数等高级特性:这些都是生成器(CMake)的职责,Ninja 只管执行
可以这么理解:make 既是构建描述语言,又是构建执行引擎。Ninja 把这两个角色分开了——你只需要用 CMake 描述构建逻辑,Ninja 负责执行,各司其职。
现代大型 C++ 项目(Chromium、LLVM)几乎都用 Ninja 做构建后端。
7、什么时候用 Ninja
| 场景 | 推荐 |
|---|---|
| 小项目(< 10 个源文件) | make 够了,没必要上 Ninja |
| 中型项目(10-100 个源文件) | Ninja 的启动速度有感知,但差异不大 |
| 大型项目(100+ 源文件) | 强烈推荐 Ninja,省的时间非常可观 |
| 持续集成(CI) | 必须 Ninja,CI 环境每次都是全新构建 |
| 配合 CMake 使用 | 几乎零成本切换,cmake -G Ninja 一步到位 |
如果你已经在用 CMake,切到 Ninja 几乎零成本——改一行生成器参数的事,换来更快的构建速度,何乐而不为。
下一篇文章,我们来认识 Google 出品的重量级构建系统——Bazel。它号称能处理成千上万个源文件的 monorepo 项目,背后是怎样的设计哲学?
每天前进一小步,就是一个新的高度!