97、性能分析工具:perf、valgrind、gprof 使用

性能分析,说白了就是给你的程序做一次「体检」。

我早年写网络服务时,经常遇到这种情况:程序跑得好好的,突然某个接口响应变慢,CPU 飙到 100%。你猜怎么着?查了半天,发现是一个循环里多了一次无意义的 memcpy。嗯,从那以后,我养成了一个习惯——上线前必须跑一遍性能分析工具。

这一讲,我带你过一遍三个最常用的工具:perfvalgrindgprof。它们各有侧重,但组合起来,基本能覆盖你 90% 的性能问题场景。

一句话总结:

  • perf:看 CPU 在忙什么,热点函数在哪。
  • valgrind:查内存泄漏、缓存命中、堆栈分析。
  • gprof:看函数调用次数和耗时分布。
性能分析工具全景图 C语言网络程序 perf CPU热点 / 采样 valgrind 内存泄漏 / 缓存 gprof 调用图 / 耗时 perf top perf record memcheck cachegrind gprof -b gprof -l

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

你想想看,一个网络程序从开发到上线,这三个工具基本能覆盖你所有的性能排查需求。我个人的工作流是:

  1. 先用 gprof 看整体函数耗时分布。
  2. 再用 perf 深入热点函数,看 CPU 到底在忙什么。
  3. 最后用 valgrind 检查内存问题,确保没有泄漏或越界。

一句话总结:

perf 看 CPU,valgrind 看内存,gprof 看调用。三个工具配合使用,你的网络程序性能问题基本无处遁形。


公众号:蓝海资料掘金营,微信 deep3321