26、构建系统性能优化:并行编译、ccache与预编译头文件
说实话,嵌入式项目的构建速度,经常让人抓狂。
我见过不少团队,改一行代码,等编译等五分钟。喝杯咖啡回来,还没编完。这其实不是代码的问题,是构建系统没调好。今天咱们就聊聊怎么让编译飞起来。
并行编译:让CPU火力全开
先讲最直接的提速手段——并行编译。说白了,就是让CPU多核同时干活。
make -j 的用法
在Makefile里,你只需要加个 -j 参数:
# 让make自己决定并行任务数
make -j
# 指定并行数,比如4核
make -j4
# 更推荐的做法:用nproc获取CPU核心数
make -j $(nproc)
我个人习惯用 make -j$(nproc)。为什么?因为不同开发机配置不一样。你写死 -j4,别人16核的机器就浪费了。
-j64,结果内存爆了,编译反而更慢。一般建议设为CPU物理核心数的1.5到2倍。
CMake中的并行控制
CMake这边更优雅。你可以通过环境变量来控制:
# 设置并行编译级别
export CMAKE_BUILD_PARALLEL_LEVEL=4
cmake --build .
也可以在CMakeLists.txt里设置:
# 全局设置,所有target都受影响
set(CMAKE_BUILD_PARALLEL_LEVEL 4)
# 或者只针对特定target
set_property(TARGET my_firmware PROPERTY JOB_POOL_COMPILE compile_job_pool)
set_property(TARGET my_firmware PROPERTY JOB_POOL_LINK link_job_pool)
嗯,这里要注意:链接阶段通常吃内存,我建议把链接的并行度设低一点。比如编译开8路,链接只开2路。
ccache:缓存编译结果
并行编译解决的是「一次编译」的速度。但实际开发中,我们经常改一行代码,然后重新编译。这时候ccache就派上用场了。
ccache的原理很简单:它缓存了编译器的输出。如果源文件没变,头文件没变,编译器参数没变,那就直接从缓存拿结果。
安装与配置
# Ubuntu/Debian
sudo apt install ccache
# macOS
brew install ccache
# 查看缓存状态
ccache -s
在CMake里启用ccache也很简单:
# 方法一:设置CMAKE_C_COMPILER_LAUNCHER
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
message(STATUS "ccache enabled")
endif()
# 方法二:更推荐的做法
set(CMAKE_C_COMPILER_LAUNCHER ccache)
set(CMAKE_CXX_COMPILER_LAUNCHER ccache)
ccache的坑
我曾经踩过一个坑:ccache缓存了错误的编译结果。原因是编译器版本变了,但ccache没意识到。解决方案:
# 设置编译器检查
export CCACHE_COMPILERCHECK=content
# 或者更狠一点,每次clean缓存
ccache -C # 清理所有缓存
另外,嵌入式开发中经常用交叉编译器。ccache默认只缓存同一编译器的结果。如果你切换了工具链,记得清缓存。
预编译头文件(PCH)
这个技术有点年头了,但在大型嵌入式项目中依然好用。
预编译头文件的思路:把那些不怎么变的标准库头文件(比如 <stdio.h>、<stdlib.h>)预先编译成二进制格式。这样每个源文件就不用重复解析这些头文件了。
GCC中的PCH
# 生成预编译头文件
gcc -x c-header my_header.h -o my_header.h.gch
# 使用:编译器会自动查找.gch文件
gcc -include my_header.h main.c -o main
在Makefile里可以这样组织:
# 生成PCH
PCH_FILE = common.h.gch
$(PCH_FILE): common.h
$(CC) -x c-header $< -o $@
# 依赖PCH
%.o: %.c $(PCH_FILE)
$(CC) -include common.h -c $< -o $@
CMake中的PCH
CMake 3.16之后原生支持PCH,用起来方便多了:
# 为target添加预编译头
target_precompile_headers(my_target PRIVATE
<vector>
<string>
<algorithm>
"my_project_common.h"
)
# 也可以只对某个文件生效
target_precompile_headers(my_target PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:<vector>>
)
知识体系总览
下面这张图总结了本章的核心逻辑:
实战建议
说了这么多,到底怎么用?我给出一个典型的嵌入式项目配置:
- 先上ccache:这是投入产出比最高的。配置简单,效果明显。
- 再开并行编译:
make -j$(nproc)或者 CMake 里设CMAKE_BUILD_PARALLEL_LEVEL。 - 最后考虑PCH:如果项目头文件特别多(比如超过50个),PCH能再省30%的时间。
最后提醒一句:性能优化要「先测量,后优化」。别一上来就搞PCH,先看看编译时间到底花在哪。用 make -d 或者 cmake --build . --verbose 看看输出,心里就有数了。