动态分析:覆盖率测试、性能剖析与调用图生成
说实话,很多嵌入式工程师写完代码就跑一下,能跑通就觉得完事了。但我在实际项目中吃过亏——代码跑是能跑,可那些没执行到的分支里藏着大坑。今天我们就聊聊怎么用工具把代码的“底裤”扒干净。
一、覆盖率测试:你代码里有多少“死胡同”?
覆盖率测试,说白了就是检查你的测试用例到底跑了多少代码。我见过一个项目,自测报告写“全部通过”,结果用gcov一跑,覆盖率才32%。剩下的68%代码从来没被执行过。
1.1 gcov:GCC自带的“照妖镜”
gcov是GCC工具链自带的,不需要额外安装。用法很简单:
// 编译时加两个关键选项
gcc -fprofile-arcs -ftest-coverage -o test_program test.c
// 运行程序
./test_program
// 生成覆盖率报告
gcov test.c
运行后会生成 .gcov 文件,里面每一行代码前面都有执行次数。比如:
-: 0:Source:test.c
-: 1:#include <stdio.h>
5: 2:int main() {
5: 3: int x = 10;
5: 4: if (x > 5) {
5: 5: printf("big\n");
-: 6: } else {
0: 7: printf("small\n");
-: 8: }
5: 9: return 0;
-: 10:}
看到第7行的 0 了吗?这说明else分支从来没被执行过。我在项目中遇到过类似情况,一个错误处理分支覆盖率为0,结果线上真的触发了那个分支,系统直接崩溃。
1.2 lcov:让报告更直观
gcov生成的文本报告看着费劲。lcov可以把结果转成HTML,带颜色标注:绿色是已覆盖,红色是未覆盖。
// 生成lcov数据
lcov --capture --directory . --output-file coverage.info
// 生成HTML报告
genhtml coverage.info --output-directory coverage_report
我个人习惯在持续集成里跑lcov,每次提交代码自动生成报告。这样团队里每个人都能看到自己的代码覆盖情况。
二、性能剖析:你的程序到底慢在哪?
“感觉程序有点慢”——这种话在项目里我听过太多次了。感觉没用,得用数据说话。gprof就是干这个的。
2.1 gprof:函数级别的性能分析
gprof能告诉你每个函数被调用了多少次、花了多少时间。用法:
// 编译时加-pg选项
gcc -pg -o test_program test.c
// 运行程序(会生成gmon.out文件)
./test_program
// 生成分析报告
gprof test_program gmon.out > analysis.txt
报告里有两张表:
| 函数名 | 调用次数 | 耗时占比 | 平均耗时(ms) |
|---|---|---|---|
| process_data | 1000 | 45.2% | 0.45 |
| sort_array | 100 | 30.1% | 3.01 |
| init_system | 1 | 15.3% | 153.00 |
你看,init_system虽然只调用一次,但占了15%的时间。这就是优化的重点。
2.2 嵌入式环境下的特殊处理
gprof在嵌入式环境里有点水土不服。为什么?因为嵌入式设备通常没有文件系统,gmon.out写不进去。
我建议的做法:
- 用串口把性能数据打印出来
- 或者用内存缓冲区暂存,再通过调试接口读取
- 实在不行,就手动插时间戳,土办法有时候最管用
三、调用图生成:看清函数之间的“关系网”
代码写多了,函数调用关系会变得很复杂。调用图能帮你理清思路。
3.1 用gprof生成调用图
gprof本身就支持生成调用图:
gprof -q test_program gmon.out
输出类似这样:
index % time self children called name
0.01 0.02 10/10 main [1]
[1] 45.2 0.01 0.02 10 process_data [1]
0.01 0.01 5/5 sort_array [2]
0.00 0.01 5/5 filter_data [3]
这表示 main 调用了 process_data 10次,process_data 又调用了 sort_array 和 filter_data 各5次。
3.2 可视化调用图
文本调用图不够直观。我一般用Graphviz把调用图转成图片:
gprof -q test_program gmon.out | gprof2dot | dot -Tpng -o callgraph.png
下面是我用SVG画的一个简化版调用图,展示核心逻辑:
四、实战经验总结
说了这么多,我总结几条实战经验:
- 覆盖率不是越高越好——嵌入式项目里,硬件初始化代码很难100%覆盖。我一般要求核心算法逻辑覆盖率达到90%以上就行。
- 性能剖析要结合场景——在PC上跑gprof的结果,和嵌入式设备上的真实表现可能有差异。我建议在目标硬件上做最终验证。
- 调用图是重构的好帮手——每次接手别人的代码,我第一件事就是生成调用图。一眼就能看出哪些函数耦合太深,哪些函数没人调用。
好了,动态分析这块就聊到这儿。工具只是辅助,关键还是要有“用数据说话”的意识。下次你觉得自己代码没问题的时候,不妨先跑一遍覆盖率看看。
公众号:蓝海资料掘金营,微信deep3321