97、性能分析工具:perf、valgrind、gprof 使用
性能分析,说白了就是给你的程序做一次「体检」。
我早年写网络服务时,经常遇到这种情况:程序跑得好好的,突然某个接口响应变慢,CPU 飙到 100%。你猜怎么着?查了半天,发现是一个循环里多了一次无意义的 memcpy。嗯,从那以后,我养成了一个习惯——上线前必须跑一遍性能分析工具。
这一讲,我带你过一遍三个最常用的工具:perf、valgrind、gprof。它们各有侧重,但组合起来,基本能覆盖你 90% 的性能问题场景。
一句话总结:
- perf:看 CPU 在忙什么,热点函数在哪。
- valgrind:查内存泄漏、缓存命中、堆栈分析。
- gprof:看函数调用次数和耗时分布。
1. perf:CPU 热点一抓一个准
perf 是 Linux 内核自带的性能分析工具。它基于硬件性能计数器,采样精度极高,几乎零开销。我个人习惯用它来定位「CPU 烧在哪里」。
举个例子,你写了一个网络数据包处理函数,感觉它慢。跑一下 perf:
# 编译时加 -g 保留符号
gcc -g -O2 server.c -o server
# 采样 10 秒
perf record -g ./server
# 生成报告
perf report
你会看到一个函数调用树,按 CPU 占用率排序。最顶上的那个,就是罪魁祸首。
我的经验:
我曾经在做一个高并发网关时,发现 perf 报告里 __memcpy_avx_unaligned 占用了 15% 的 CPU。追下去发现是每次解析 HTTP 头都复制了一遍整个 buffer。改成指针引用后,CPU 直接降了 8%。
perf 的常用子命令:
| 命令 | 作用 |
|---|---|
| perf top | 实时查看当前系统热点函数 |
| perf record | 采样记录性能数据 |
| perf report | 展示采样结果 |
| perf stat | 统计程序运行时的硬件事件(如 cache miss) |
注意:
perf 需要 root 权限或 /proc/sys/kernel/perf_event_paranoid 设置为 1 以下。否则你会看到「perf_event_open failed」的错误。
2. valgrind:内存问题的照妖镜
valgrind 不是用来测 CPU 的,它主要查内存泄漏、非法读写、缓存效率。你想想看,一个网络程序跑着跑着内存越占越多,最后 OOM 被 kill 掉——这种问题 valgrind 一查一个准。
基本用法:
valgrind --tool=memcheck --leak-check=full ./server
它会输出类似这样的信息:
==12345== 40 bytes in 1 blocks are definitely lost in loss record 1 of 5
==12345== at 0x4C2B0E0: malloc (vg_replace_malloc.c:309)
==12345== by 0x4005A4: create_packet (packet.c:23)
==12345== by 0x4006B2: handle_client (server.c:45)
看到没?它直接告诉你:第 23 行 malloc 的内存没 free。你顺着这个线索去修,问题就解决了。
valgrind 的常用工具:
- memcheck:检测内存泄漏、越界、使用未初始化内存。
- cachegrind:模拟 CPU 缓存,分析 cache miss 率。
- helgrind:检测多线程竞争条件。
- massif:分析堆内存使用情况。
避坑指南:
我曾经在调试一个 epoll 事件循环时,valgrind 报了很多「conditional jump depends on uninitialized value」。一开始我以为是 valgrind 误报,后来发现是结构体里一个字段没初始化,导致 epoll_event 的 events 字段随机值。嗯,从那以后我每次定义结构体都习惯用 = {0} 清零。
性能影响:
valgrind 会让程序运行慢 10-50 倍。所以别在生产环境直接跑,建议在测试环境或压力测试时使用。
3. gprof:函数调用关系的「家谱」
gprof 是 GNU 的工具,它通过编译时插入的 profiling 代码,统计每个函数的调用次数和耗时。说白了,它能告诉你「哪个函数被调了最多」、「哪个函数最耗时」。
用法很简单:
# 编译时加 -pg 选项
gcc -pg -g server.c -o server
# 运行程序(会生成 gmon.out)
./server
# 生成分析报告
gprof ./server gmon.out > report.txt
打开 report.txt,你会看到两部分:
- Flat profile:每个函数的耗时占比,按降序排列。
- Call graph:函数调用关系图,显示谁调了谁,耗时多少。
举个例子,你可能会看到:
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
45.2 0.19 0.19 1000 0.19 0.42 process_packet
30.1 0.32 0.13 500 0.26 0.26 parse_header
15.0 0.38 0.06 200 0.30 0.30 encrypt_data
看到没?process_packet 占了 45% 的时间。你该优化它。
我的习惯:
我一般先用 gprof 看整体分布,再用 perf 深入热点函数内部。gprof 适合「宏观」分析,perf 适合「微观」定位。两者互补,效果很好。
注意:
gprof 只统计函数级别的耗时,不统计内联函数和系统调用。而且它需要程序正常退出(调用 exit 或 return),否则 gmon.out 不会生成。
4. 三个工具怎么选?
我直接给你一个决策思路:
| 场景 | 推荐工具 |
|---|---|
| CPU 占用高,不知道哪段代码在忙 | perf |
| 内存泄漏、非法访问、缓存效率低 | valgrind |
| 函数调用关系复杂,想优化调用路径 | gprof |
| 多线程竞争、死锁 | valgrind (helgrind) |
| 实时监控线上服务热点 | perf top |
你想想看,一个网络程序从开发到上线,这三个工具基本能覆盖你所有的性能排查需求。我个人的工作流是:
- 先用 gprof 看整体函数耗时分布。
- 再用 perf 深入热点函数,看 CPU 到底在忙什么。
- 最后用 valgrind 检查内存问题,确保没有泄漏或越界。
一句话总结:
perf 看 CPU,valgrind 看内存,gprof 看调用。三个工具配合使用,你的网络程序性能问题基本无处遁形。
公众号:蓝海资料掘金营,微信 deep3321