C++ 编译加速简介

C++ 编译加速简介

引言

程序执行性能一直是 C++ 的优势。然而相比于其他语言, C++ 在大型项目中的编译速度简直慢的令人发指;

其主要原因是 C++ “头文件-源文件”的编译方式。在 C++ 中,编译单元以文件为单位,每个源文件需独立解析所有包含的头文件,若某个头文件被 N 个源文件引用,则此头文件需重复解析 N 次。同时,若此头文件发生改变,则所有引用此头文件的文件均需重新编译;

因此,大型 C++ 工程的编译会产生大量重复解析文件操作。开发人员需要根据 C++ 编译方式的特点,应用各种手段来缓解上述问题;

加速编译方法

有很多行之有效的方法来应对 C++ 编译速度问题,现列举其中六种常用方法:

Include Guard

这是编写头文件的规范,主要作用是防止头文件在编译单元中被重复引用(尤其是类定义在头文件中,多次引用会产生重复定义错误)。需要注意的是, #program once 在有些编译器中并不支持,所以还是使用常用的 #ifndef-#define-#endif 更靠谱;

减少依赖

头文件冗余是大型 C++ 项目中的常见问题,这会导致编译时间增加。冗余头文件无需手动清理,有很多工具可以自动化完成这一工作,如 Include-What-You-Use 等;

另一种常见的依赖问题是原始代码的层级设计并不合理。对于这种情况,就需要分析代码的各个模块,利用“依赖反转”原则对变动频率相差过大的模块代码进行手动修改;

前置声明与 PImpl 技法

类前置声明与 PImpl 技法搭配使用,建立”编译防火墙“,这样就减少了头文件的引用,同时当类实现细节变更时,类的使用者不必重新编译;

预编译头文件 PCH

将那些不常变更且被大量引用的头文件集合进行预编译并保存为缓存,当编译时即可利用已保存的缓存而不用每次都重新解析;

使用动态库或静态库

将不常变动的基础库编译为动态库或静态库是常规操作,故不赘述;

提升硬件与并行/分布式编译

大力出奇迹,故不赘述;

总结

在上述列举的六种常用方法中:前三种是编写 C++ 代码的规范,在编码过程中应时刻铭记并使用;后三种则是针对工程构建的方法,是编码过程之外所进行的操作;在实践中可结合具体项目使用上述六种常用方法以解决编译速度问题;

需要注意的是, 除了上述提到的因素,模板也是 C++ 项目编译时间漫长原因之一,但模板在 C++ 中作用重大,使用广泛,故开发者需自行酌情考虑;

最后,随着 C++ 20 标准中模块的引入, C++ 编译速度这一顽疾得到了根本性解决,但是其特性在工程中广泛应用还需一定时间,开发者可先尝鲜试用;

参考


本文作者: 王同学