编译速度显著提升的根本原因是模块消除了重复解析和宏污染,使每个接口只处理一次;模块二进制接口(如.pcm)被直接导入,跳过预处理、词法及语法分析,预处理开销归零,依赖图不爆炸,且支持跨TU优化。

编译速度为何能显著提升?
根本原因在于模块(module)消除了重复解析和宏污染,让每个接口只被处理一次。传统头文件每次被 #include 就要重新预处理、词法分析、语法分析——哪怕同一份 vector 被 50 个源文件包含,就做 50 次相同工作;而模块二进制接口(如 .pcm 文件)生成后,其他翻译单元直接导入,跳过所有前端阶段。
- 预处理开销归零:没有
#define展开、#ifdef判定、行拼接等操作 - 头文件依赖图不再线性爆炸:
import std.core;不会隐式拖入整个iostream或string的全部实现细节 - 编译器可跨 TU 做更激进的内联与常量传播,因接口契约更稳定(无宏干扰)
实际项目中哪些场景提速最明显?
不是所有代码都受益均等。以下几类改动后实测加速比通常 >2×:
- 大型模板库重度使用者(如 Eigen、Boost.Hana):模板定义不再反复实例化解析
- 含大量条件编译的跨平台代码(
#ifdef _WIN32/#ifdef __linux__):模块接口段不参与预处理,逻辑更干净 - 单个头文件被数百个
.cpp包含(如公共配置头config.h):改用模块后,该接口只编译一次 - 构建系统未启用 PCH 或 PCH 维护成本高的项目:模块天然替代 PCH,且无需手动管理“预编译什么”
为什么 import 不能完全替代 #include?
当前主流编译器(MSVC / Clang / GCC)对模块的支持仍有限制,尤其涉及遗留代码时:
- C 风格头文件(如
)无法直接import,需通过模块接口单元(MIU)桥接,例如 MSVC 的import是特例支持,并非标准行为 - 宏定义无法跨模块导出:
#define LOG(x) printf("%s\n", x)写在模块里,导入方看不到——这既是限制,也是优势(避免宏污染) - 某些 IDE 和构建系统(如旧版 CMake)尚未原生识别
.ixx或.cppm,需手动配置规则,容易漏掉依赖声明 - 模块接口单元(
export module)中不能出现未定义行为代码(如未初始化变量),编译器会在接口编译阶段报错,而头文件中这类问题可能延迟到实例化才暴露
一个最小可验证提速对比示例
假设有一个常用工具头 utils.h,被 100 个 .cpp 文件包含;改写为模块后,仅需一次接口编译:
立即学习“C++免费学习笔记(深入)”;
// utils.ixx
export module utils;
export namespace util {
inline int square(int x) { return x * x; }
}
对应传统头文件方式需重复解析百次,而模块方式:
- 首次构建:编译
utils.ixx→ 生成utils.pcm(耗时 ≈ 单次头文件解析 ×1.5,含序列化开销) - 后续构建:100 个
.cpp全部import utils;,仅加载.pcm二进制,耗时 ≈ 单次头文件解析 ×0.1 - 修改
utils.ixx后,仅重编.pcm+ 重新导入它的 TU,而非所有包含者
真实大型项目中,模块化后 clean build 时间下降 30–60%,incremental build 可快 5–10 倍——但前提是模块粒度合理,且避免把所有东西塞进一个巨无霸模块里。










