第6章 性能分析工具
完整 485 页的 《编译与调试完全指南》见: lengjingzju-notes发布页
性能分析是软件优化的第一步。Linux 提供了从用户态到内核态、从粗粒度到细粒度的完整性能分析工具链。本章重点介绍两大核心工具:perf(Linux 内核自带的性能计数器子系统)和 gprof(GNU 传统的函数级性能剖析工具),并回顾与性能分析配合的编译选项。
#mermaid-svg-kKVdiELgSXC2IxyT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kKVdiELgSXC2IxyT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kKVdiELgSXC2IxyT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kKVdiELgSXC2IxyT .error-icon{fill:#552222;}#mermaid-svg-kKVdiELgSXC2IxyT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kKVdiELgSXC2IxyT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kKVdiELgSXC2IxyT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kKVdiELgSXC2IxyT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kKVdiELgSXC2IxyT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kKVdiELgSXC2IxyT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kKVdiELgSXC2IxyT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kKVdiELgSXC2IxyT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kKVdiELgSXC2IxyT .marker.cross{stroke:#333333;}#mermaid-svg-kKVdiELgSXC2IxyT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kKVdiELgSXC2IxyT p{margin:0;}#mermaid-svg-kKVdiELgSXC2IxyT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kKVdiELgSXC2IxyT .cluster-label text{fill:#333;}#mermaid-svg-kKVdiELgSXC2IxyT .cluster-label span{color:#333;}#mermaid-svg-kKVdiELgSXC2IxyT .cluster-label span p{background-color:transparent;}#mermaid-svg-kKVdiELgSXC2IxyT .label text,#mermaid-svg-kKVdiELgSXC2IxyT span{fill:#333;color:#333;}#mermaid-svg-kKVdiELgSXC2IxyT .node rect,#mermaid-svg-kKVdiELgSXC2IxyT .node circle,#mermaid-svg-kKVdiELgSXC2IxyT .node ellipse,#mermaid-svg-kKVdiELgSXC2IxyT .node polygon,#mermaid-svg-kKVdiELgSXC2IxyT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kKVdiELgSXC2IxyT .rough-node .label text,#mermaid-svg-kKVdiELgSXC2IxyT .node .label text,#mermaid-svg-kKVdiELgSXC2IxyT .image-shape .label,#mermaid-svg-kKVdiELgSXC2IxyT .icon-shape .label{text-anchor:middle;}#mermaid-svg-kKVdiELgSXC2IxyT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kKVdiELgSXC2IxyT .rough-node .label,#mermaid-svg-kKVdiELgSXC2IxyT .node .label,#mermaid-svg-kKVdiELgSXC2IxyT .image-shape .label,#mermaid-svg-kKVdiELgSXC2IxyT .icon-shape .label{text-align:center;}#mermaid-svg-kKVdiELgSXC2IxyT .node.clickable{cursor:pointer;}#mermaid-svg-kKVdiELgSXC2IxyT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kKVdiELgSXC2IxyT .arrowheadPath{fill:#333333;}#mermaid-svg-kKVdiELgSXC2IxyT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kKVdiELgSXC2IxyT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kKVdiELgSXC2IxyT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kKVdiELgSXC2IxyT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kKVdiELgSXC2IxyT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kKVdiELgSXC2IxyT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kKVdiELgSXC2IxyT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kKVdiELgSXC2IxyT .cluster text{fill:#333;}#mermaid-svg-kKVdiELgSXC2IxyT .cluster span{color:#333;}#mermaid-svg-kKVdiELgSXC2IxyT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kKVdiELgSXC2IxyT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kKVdiELgSXC2IxyT rect.text{fill:none;stroke-width:0;}#mermaid-svg-kKVdiELgSXC2IxyT .icon-shape,#mermaid-svg-kKVdiELgSXC2IxyT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kKVdiELgSXC2IxyT .icon-shape p,#mermaid-svg-kKVdiELgSXC2IxyT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kKVdiELgSXC2IxyT .icon-shape .label rect,#mermaid-svg-kKVdiELgSXC2IxyT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kKVdiELgSXC2IxyT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kKVdiELgSXC2IxyT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kKVdiELgSXC2IxyT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 分析维度
性能分析工具
perf
内核性能计数器
gprof
函数级剖析
PGO
反馈优化
CPU热点
缓存行为
分支预测
调度延迟
锁竞争
内存分配
6.1 perf 性能分析
perf 是内置于 Linux 内核源码树中的性能剖析(profiling)工具,基于硬件性能计数器(PMU)和软件事件,能够以极低的开销对系统进行全栈性能分析。它位于内核源码树的 tools/perf 目录下,随内核版本一起发布。
6.1.1 perf 架构与子系统(perf 工具总览)
perf 工具集包含多个子命令,覆盖从数据采集到分析展示的完整流程:
#mermaid-svg-G9DOylQiNfxXNJM5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-G9DOylQiNfxXNJM5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-G9DOylQiNfxXNJM5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-G9DOylQiNfxXNJM5 .error-icon{fill:#552222;}#mermaid-svg-G9DOylQiNfxXNJM5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-G9DOylQiNfxXNJM5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-G9DOylQiNfxXNJM5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-G9DOylQiNfxXNJM5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-G9DOylQiNfxXNJM5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-G9DOylQiNfxXNJM5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-G9DOylQiNfxXNJM5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-G9DOylQiNfxXNJM5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-G9DOylQiNfxXNJM5 .marker.cross{stroke:#333333;}#mermaid-svg-G9DOylQiNfxXNJM5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-G9DOylQiNfxXNJM5 p{margin:0;}#mermaid-svg-G9DOylQiNfxXNJM5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-G9DOylQiNfxXNJM5 .cluster-label text{fill:#333;}#mermaid-svg-G9DOylQiNfxXNJM5 .cluster-label span{color:#333;}#mermaid-svg-G9DOylQiNfxXNJM5 .cluster-label span p{background-color:transparent;}#mermaid-svg-G9DOylQiNfxXNJM5 .label text,#mermaid-svg-G9DOylQiNfxXNJM5 span{fill:#333;color:#333;}#mermaid-svg-G9DOylQiNfxXNJM5 .node rect,#mermaid-svg-G9DOylQiNfxXNJM5 .node circle,#mermaid-svg-G9DOylQiNfxXNJM5 .node ellipse,#mermaid-svg-G9DOylQiNfxXNJM5 .node polygon,#mermaid-svg-G9DOylQiNfxXNJM5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-G9DOylQiNfxXNJM5 .rough-node .label text,#mermaid-svg-G9DOylQiNfxXNJM5 .node .label text,#mermaid-svg-G9DOylQiNfxXNJM5 .image-shape .label,#mermaid-svg-G9DOylQiNfxXNJM5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-G9DOylQiNfxXNJM5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-G9DOylQiNfxXNJM5 .rough-node .label,#mermaid-svg-G9DOylQiNfxXNJM5 .node .label,#mermaid-svg-G9DOylQiNfxXNJM5 .image-shape .label,#mermaid-svg-G9DOylQiNfxXNJM5 .icon-shape .label{text-align:center;}#mermaid-svg-G9DOylQiNfxXNJM5 .node.clickable{cursor:pointer;}#mermaid-svg-G9DOylQiNfxXNJM5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-G9DOylQiNfxXNJM5 .arrowheadPath{fill:#333333;}#mermaid-svg-G9DOylQiNfxXNJM5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-G9DOylQiNfxXNJM5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-G9DOylQiNfxXNJM5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-G9DOylQiNfxXNJM5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-G9DOylQiNfxXNJM5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-G9DOylQiNfxXNJM5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-G9DOylQiNfxXNJM5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-G9DOylQiNfxXNJM5 .cluster text{fill:#333;}#mermaid-svg-G9DOylQiNfxXNJM5 .cluster span{color:#333;}#mermaid-svg-G9DOylQiNfxXNJM5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-G9DOylQiNfxXNJM5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-G9DOylQiNfxXNJM5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-G9DOylQiNfxXNJM5 .icon-shape,#mermaid-svg-G9DOylQiNfxXNJM5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-G9DOylQiNfxXNJM5 .icon-shape p,#mermaid-svg-G9DOylQiNfxXNJM5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-G9DOylQiNfxXNJM5 .icon-shape .label rect,#mermaid-svg-G9DOylQiNfxXNJM5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-G9DOylQiNfxXNJM5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-G9DOylQiNfxXNJM5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-G9DOylQiNfxXNJM5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 动态与基准
子系统
分析
采集
perf 工具
perf record
采样记录
perf stat
计数器统计
perf top
实时监控
perf trace
系统调用跟踪
perf report
报告分析
perf script
脚本输出
perf annotate
源码级分析
perf sched
调度分析
perf lock
锁分析
perf kmem
内存分析
perf c2c
缓存一致性
perf probe
动态追踪点
perf bench
基准测试
perf 子命令功能总表:
| 子命令 | 功能说明 | 典型场景 |
|---|---|---|
| perf stat | 性能计数器统计(指令数、缓存命中率等) | 快速评估程序整体性能特征 |
| perf record | 记录性能数据到文件中 | 采集性能样本供后续分析 |
| perf report | 分析 perf record 记录的数据 | 定位热点函数 |
| perf top | 实时显示 CPU 热点函数 | 快速定位当前系统热点 |
| perf trace | 类似 strace 的系统调用跟踪 | 分析系统调用开销 |
| perf sched | 调度器分析(延迟、唤醒延迟) | 分析调度延迟问题 |
| perf kmem | 内核内存分配分析 | 分析内核内存使用 |
| perf lock | 锁竞争分析 | 定位锁瓶颈 |
| perf c2c | 缓存一致性分析(False Sharing 检测) | 多线程缓存性能问题 |
| perf bench | 内核子系统基准测试 | 性能基准对比 |
| perf probe | 动态添加追踪点(kprobes/uprobes) | 无需重编译的动态追踪 |
| perf script | 读取 perf.data 并显示追踪信息 | 生成火焰图的前置步骤 |
| perf annotate | 源码级性能分析 | 定位热点指令 |
权限配置
perf 默认需要 root 权限或调整 perf_event_paranoid 参数:
bash
# 查看当前权限级别
cat /proc/sys/kernel/perf_event_paranoid
# 临时放开权限(-1=无限制,0=允许访问CPU事件,1=允许用户态事件,2=仅内核态)
sudo sysctl -w kernel.perf_event_paranoid=-1
# 永久配置(/etc/sysctl.conf)
kernel.perf_event_paranoid = -1
perf_event_paranoid 权限级别说明:
| 值 | 权限说明 | 适用场景 |
|---|---|---|
| -1 | 无限制,所有用户可访问所有事件 | 开发/测试环境 |
| 0 | 允许访问 CPU 级别事件(不含 raw tracepoint) | 性能分析 |
| 1 | 仅允许用户态事件(默认值) | 普通用户分析自身程序 |
| 2 | 仅允许内核态事件 | 受限环境 |
6.1.2 perf stat(性能计数器统计)
perf stat 通过读取硬件性能计数器,统计程序运行期间的关键性能指标。它不采样,而是精确计数,开销极低。
基本用法
bash
perf stat [options] command [args]
常用选项
| 选项 | 说明 | 示例 |
|---|---|---|
| -e \ | 指定要统计的事件 | perf stat -e cycles,instructions ./program |
| -a | 系统范围统计(所有CPU) | perf stat -a sleep 5 |
| -p \ | 统计指定进程 | perf stat -p 1234 |
| -r \ | 重复运行取平均值 | perf stat -r 5 ./program |
| -B | 显示大页统计 | perf stat -B ./program |
| -d | 显示详细缓存统计 | perf stat -d ./program |
| -x \ | 指定输出分隔符(便于脚本解析) | perf stat -x, ./program |
常用硬件事件
| 事件名 | 说明 | 分析意义 |
|---|---|---|
| cycles | CPU 周期数 | 反映程序执行时间 |
| instructions | 执行的指令数 | 反映程序工作量 |
| cache-references | 缓存引用次数 | 内存访问频率 |
| cache-misses | 缓存未命中次数 | 缓存效率 |
| branches | 分支指令数 | 控制流复杂度 |
| branch-misses | 分支预测失败次数 | 分支预测效率 |
| L1-dcache-loads | L1 数据缓存加载 | L1 缓存行为 |
| L1-dcache-load-misses | L1 数据缓存加载未命中 | L1 缓存效率 |
| LLC-loads | 最后一级缓存加载 | LLC 缓存行为 |
| LLC-load-misses | 最后一级缓存加载未命中 | 内存带宽压力 |
| dTLB-loads | 数据 TLB 加载 | TLB 行为 |
| dTLB-load-misses | 数据 TLB 未命中 | 页面局部性 |
| page-faults | 缺页异常次数 | 内存分配行为 |
| context-switches | 上下文切换次数 | 调度压力 |
| cpu-migrations | CPU 迁移次数 | 亲和性问题 |
实战示例与输出解读
bash
$ perf stat ./myop 3 5
Performance counter stats for './myop 3 5':
1.234567 task-clock (msec) # 0.876 CPUs utilized
2 context-switches # 0.002 M/sec
0 cpu-migrations # 0.000 K/sec
56 page-faults # 0.045 M/sec
3,456,789 cycles # 2.800 GHz
5,678,901 instructions # 1.64 insn per cycle
1,234,567 branches # 1000.000 M/sec
12,345 branch-misses # 1.00% of all branches
0.001409234 seconds time elapsed
输出字段解读:
| 字段 | 说明 |
|---|---|
| task-clock (msec) | 任务占用的 CPU 时间(毫秒) |
| CPUs utilized | 实际使用的 CPU 核数(task-clock / wall-clock) |
| context-switches | 上下文切换次数,过多说明线程竞争激烈 |
| cpu-migrations | CPU 迁移次数,过多说明未绑定 CPU |
| page-faults | 缺页异常次数,反映首次内存分配 |
| cycles | CPU 周期总数 |
| instructions | 执行的指令总数 |
| insn per cycle | IPC(每周期指令数),核心性能指标 |
| branches | 分支指令总数 |
| branch-misses | 分支预测失败次数及占比 |
| seconds time elapsed | 程序实际运行时间(墙钟时间) |
关键指标解读
| 指标 | 计算方式 | 健康范围 | 异常处理 |
|---|---|---|---|
| IPC(Instructions Per Cycle) | instructions / cycles | >1.0 为良好 | <0.5 说明存在严重停顿(缓存未命中、分支预测失败等) |
| 缓存命中率 | 1 - cache-misses/cache-references | >95% 为良好 | <90% 需要优化数据访问模式 |
| 分支预测成功率 | 1 - branch-misses/branches | >95% 为良好 | <90% 考虑优化分支逻辑或使用 __builtin_expect |
查看可用事件列表
bash
# 列出所有可用事件
perf list
# 按类别过滤
perf list hw # 硬件事件
perf list sw # 软件事件
perf list cache # 缓存事件
perf list tracepoint # 追踪点事件
实践建议
| 分析目标 | 推荐命令 |
|---|---|
| 快速评估程序性能 | perf stat ./program |
| 详细缓存分析 | perf stat -d ./program(显示 L1/LLC/TLB 详细数据) |
| 多次运行取平均 | perf stat -r 10 ./program |
| 分析运行中的服务 | perf stat -p <PID> sleep 10 |
| 系统整体评估 | perf stat -a sleep 10 |
6.1.3 perf record / perf report(采样记录与报告分析)
perf record 通过定时采样(默认基于 CPU 周期)记录性能数据到 perf.data 文件,perf report 对采样数据进行统计分析,找出热点函数。
perf record 基本用法
bash
# 记录程序运行的性能数据
perf record [options] command [args]
# 记录运行中进程
perf record [options] -p <PID>
# 系统范围记录
perf record [options] -a
perf record 常用选项
| 选项 | 说明 | 示例 |
|---|---|---|
| -F \ | 采样频率(Hz),默认 4000 | perf record -F 999 -g ./program |
| -g | 记录调用图(call graph) | 生成火焰图时必须 |
| -p \ | 指定要采样的进程 | perf record -g -p 1234 |
| -e \ | 指定采样事件 | perf record -e cache-misses ./program |
| -C \ | 指定 CPU | perf record -C 0,1 -a |
| -o \ | 指定输出文件 | perf record -o my.data ./program |
| -a | 系统范围采样(所有 CPU) | perf record -a sleep 10 |
| --call-graph dwarf | 使用 DWARF 信息进行栈回溯 | 帧指针被优化掉时使用 |
| --call-graph fp | 使用帧指针进行栈回溯(默认) | 需要 -fno-omit-frame-pointer |
| -c \ | 每 count 个事件采样一次 | perf record -c 100000 ./program |
perf report 基本用法
bash
# 交互式查看报告
perf report
# 指定输入文件
perf report -i perf.data
# 按调用图显示
perf report -g
# 输出为文本格式
perf report --stdio
perf report 交互键
| 键 | 说明 |
|---|---|
| Enter | 展开/折叠调用图 |
| + | 展开当前节点 |
| - | 折叠当前节点 |
| a | 反汇编当前函数(进入 annotate) |
| / | 搜索函数名 |
| q | 退出 |
完整实战示例
bash
# 1. 编译(保留帧指针和调试信息)
gcc -O2 -g -fno-omit-frame-pointer -o program program.c
# 2. 记录性能数据(采样频率99Hz,记录调用图)
perf record -F 99 -g ./program
# 3. 查看报告
perf report
# 4. 记录运行中的服务进程(采样30秒)
perf record -F 99 -g -p $(pidof myserver) -- sleep 30
# 5. 仅关注缓存未命中事件
perf record -e cache-misses -g ./program
perf report 输出示例与解读
# Overhead Command Shared Object Symbol
# ........ ........ .................. ..............................
45.23% program program [.] compute_hash
23.11% program libc-2.31.so [.] malloc
12.45% program program [.] process_data
8.67% program [kernel.kallsyms] [k] page_fault
5.32% program program [.] main
| 字段 | 说明 |
|---|---|
| Overhead | 该函数占总采样数的百分比,是定位热点的关键指标 |
| Command | 产生采样的进程名 |
| Shared Object | 函数所在的共享库或可执行文件 |
| Symbol | 函数符号名,[.] 表示用户态,[k] 表示内核态 |
6.1.4 perf top(实时热点监控)
perf top 实时显示系统中占用 CPU 最多的函数,类似于 top 命令,但针对的是函数级别。
基本用法
bash
perf top [options]
常用选项
| 选项 | 说明 | 示例 |
|---|---|---|
| -p \ | 仅监控指定进程 | perf top -p 1234 |
| -C \ | 仅监控指定 CPU | perf top -C 0 |
| -e \ | 指定监控事件 | perf top -e cache-misses |
| -F \ | 采样频率 | perf top -F 999 |
| -g | 显示调用图 | perf top -g |
| -K | 不显示内核函数 | perf top -K |
| -U | 不显示用户态函数 | perf top -U |
交互键
| 键 | 说明 |
|---|---|
| h | 显示帮助 |
| q | 退出 |
| p | 按进程过滤 |
| d | 设置刷新间隔(秒) |
| f | 按函数名过滤 |
| Enter | 进入选中函数的 annotate 视图 |
| a | 反汇编当前函数 |
| E | 展开调用图 |
实战示例
bash
# 实时查看系统热点
perf top
# 仅查看指定进程的热点
perf top -p $(pidof nginx)
# 查看缓存未命中的热点
perf top -e cache-misses
# 查看指定CPU的热点
perf top -C 2
perf top 与 perf record + perf report 的区别
| 对比项 | perf top | perf record + perf report |
|---|---|---|
| 数据保存 | 不保存,实时显示 | 保存到 perf.data 文件 |
| 适用场景 | 快速查看当前热点 | 需要事后深入分析 |
| 交互性 | 实时刷新 | 静态分析 |
| 调用图 | 支持(-g) |
支持(-g) |
| 火焰图 | 不支持 | 支持(配合 perf script) |
6.1.5 perf trace(系统调用跟踪)
perf trace 跟踪系统调用,功能类似 strace,但开销更低(基于 eBPF/ftrace 而非 ptrace)。
基本用法
bash
# 跟踪程序的系统调用
perf trace ./program
# 跟踪运行中的进程
perf trace -p <PID>
# 系统范围跟踪
perf trace -a
常用选项
| 选项 | 说明 | 示例 |
|---|---|---|
| -p \ | 跟踪指定进程 | perf trace -p 1234 |
| -e \ | 过滤指定系统调用 | perf trace -e open,read,write |
| -s | 显示系统调用统计摘要 | perf trace -s ./program |
| -T | 显示时间戳 | perf trace -T ./program |
| -D \ | 仅显示耗时超过指定毫秒的调用 | perf trace -D 10 ./program |
与 strace 的对比
| 特性 | perf trace | strace |
|---|---|---|
| 实现机制 | eBPF/ftrace | ptrace |
| 性能开销 | 低(~2-5%) | 高(~50-100%) |
| 输出详细度 | 中等 | 高(显示参数和返回值) |
| 适用场景 | 生产环境快速诊断 | 开发环境详细调试 |
实战示例
bash
# 统计程序的系统调用分布
$ perf trace -s ./myop 3 5
Summary of events:
myop (12345), 5 events, 100.0%
syscall calls total min avg max
-------- -------- -------- -------- -------- --------
write 2 0.012 ms 0.005 0.006 0.007
read 1 0.008 ms 0.008 0.008 0.008
brk 1 0.003 ms 0.003 0.003 0.003
exit_group 1 0.001 ms 0.001 0.001 0.001
6.1.6 perf sched(调度器分析)
perf sched 分析内核调度器行为,包括调度延迟、唤醒延迟、迁移等。
子命令
| 命令 | 说明 |
|---|---|
| perf sched record | 记录调度事件 |
| perf sched latency | 显示调度延迟(任务从唤醒到实际运行的延迟) |
| perf sched map | 显示 CPU 任务映射(时间线上各 CPU 运行的任务) |
| perf sched trace | 显示调度追踪详情 |
| perf sched script | 生成脚本输出 |
| perf sched replay | 重放调度事件 |
实战示例
bash
# 1. 记录调度事件(10秒)
perf sched record -a -- sleep 10
# 2. 查看调度延迟
perf sched latency
# 3. 查看CPU任务映射
perf sched map
perf sched latency 输出解读
Task | Runtime ms | Switches | Average delay ms | Maximum delay ms
-----------------------+--------------+------------+--------------------+------------------
nginx:1234 | 1234.567 | 12345 | 0.234 | 12.345
worker:5678 | 987.654 | 9876 | 0.123 | 5.678
| 字段 | 含义 |
|---|---|
| Runtime ms | 任务总运行时间 |
| Switches | 上下文切换次数 |
| Average delay ms | 任务从被唤醒到实际获得 CPU 的平均延迟 |
| Maximum delay ms | 最大调度延迟,用于发现偶发的调度抖动 |
perf sched map 输出解读
perf sched map 以时间线方式显示各 CPU 上运行的任务,每个字符代表一个任务,相同字符表示同一任务。可以直观看到任务在不同 CPU 之间的迁移情况。
易错点
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 调度延迟数据为空 | 未使用 -a 全系统记录 |
确保使用 perf sched record -a |
| 数据量过大 | 记录时间过长 | 缩短记录时间或过滤特定进程 |
| Maximum delay 异常大 | 可能存在 CPU 被长时间占用 | 检查是否有 RT 任务或中断风暴 |
6.1.7 perf lock(锁竞争分析)
perf lock 分析锁竞争情况,帮助定位多线程程序中的锁瓶颈。
基本用法
bash
# 1. 记录锁事件
perf lock record -a -- sleep 10
# 或记录指定进程
perf lock record -p <PID> -- sleep 10
# 2. 分析锁竞争报告
perf lock report
perf lock report 输出字段
| 字段 | 说明 |
|---|---|
| Name | 锁的名称 |
| acquired | 锁被获取的次数 |
| contended | 发生竞争的次数 |
| avg wait | 平均等待时间 |
| max wait | 最大等待时间 |
实战示例
bash
# 记录多线程程序的锁事件
$ perf lock record -p $(pidof myserver) -- sleep 30
# 查看锁竞争报告
$ perf lock report
Name acquired contended avg wait (ns) max wait (ns)
&pool->lock 1250000 35000 1250 98000
&cache->mutex 890000 12000 890 45000
&queue->spin 560000 8000 650 32000
输出解读与分析要点
| 分析指标 | 判断标准 | 优化方向 |
|---|---|---|
| contended / acquired 比值 | >5% 说明竞争激烈 | 减小锁粒度或使用无锁结构 |
| avg wait 过大 | 锁持有时间过长 | 缩短临界区代码 |
| max wait 远大于 avg wait | 存在偶发的严重竞争 | 检查是否有优先级反转 |
注意事项
perf lock依赖内核的CONFIG_LOCK_STAT配置选项- 需要 root 权限运行
- 对于用户态的 pthread 锁,需要内核支持 uprobes
6.1.8 perf c2c(伪共享检测)
perf c2c(Cache-to-Cache)检测 False Sharing(伪共享)问题。当多个线程频繁修改同一缓存行中的不同变量时,会导致缓存行在多个 CPU 之间反复失效和同步,严重影响性能。
基本用法
bash
# 1. 记录缓存一致性事件
perf c2c record ./program
# 或系统范围
perf c2c record -a -- sleep 10
# 2. 生成报告
perf c2c report
False Sharing 的典型特征
- 多个线程各自修改独立的变量,但这些变量恰好位于同一缓存行(通常 64 字节)
perf c2c report会显示高 HITM(Hit Modified)率的内存地址
perf c2c report 输出关键字段
| 字段 | 说明 |
|---|---|
| HITM | Hit Modified,表示缓存行被其他 CPU 修改后再次访问的次数 |
| Total Load | 总加载次数 |
| Store Refs | 存储引用次数 |
| Data Offset | 数据在缓存行中的偏移 |
| Code address | 产生访问的代码地址 |
| Symbol | 对应的函数符号 |
解决方法
使用 __attribute__((aligned(64))) 或填充(padding)将变量对齐到不同缓存行。
c
// 存在 False Sharing 的代码
struct {
int counter_a; // 线程0频繁写入
int counter_b; // 线程1频繁写入
} shared_data; // counter_a 和 counter_b 可能在同一缓存行
// 修复:对齐到缓存行边界
struct {
int counter_a __attribute__((aligned(64))); // 独占一个缓存行
int counter_b __attribute__((aligned(64))); // 独占另一个缓存行
} shared_data;
实战示例
bash
# 记录并分析
$ perf c2c record ./multithread_program
$ perf c2c report
# 查看高 HITM 率的地址
$ perf c2c report --sort=hitm
6.1.9 perf kmem(内核内存分配分析)
perf kmem 分析内核内存分配行为,跟踪 kmalloc、kfree、alloc_pages 等内核内存分配函数。
基本用法
bash
# 1. 记录内核内存分配事件
perf kmem record -- sleep 10
# 2. 查看统计信息
perf kmem stat
# 按分配大小排序
perf kmem stat --sort bytes
# 按调用者排序
perf kmem stat --sort caller
常用选项
| 选项 | 说明 | 示例 |
|---|---|---|
| --sort \ | 排序方式(bytes/calls/frag/page/caller) | perf kmem stat --sort bytes |
| --line \ | 显示前 n 条记录 | perf kmem stat --line 20 |
| --alloc | 仅统计分配 | perf kmem stat --alloc |
| --free | 仅统计释放 | perf kmem stat --free |
| --caller | 显示调用者信息 | perf kmem stat --caller |
| --page | 统计页面分配 | perf kmem stat --page |
perf kmem stat 输出示例
SUMMARY
=======
Total bytes requested: 1048576
Total bytes wasted: 0
Total bytes allocated: 1048576
Net total bytes allocated: 1048576
Total bytes freed: 0
Net total bytes freed: 0
Callsite | Alloc Bytes | Free Bytes | Frag
------------------------+-------------+------------+------
0xffffffff81234567 | 4096 | 0 | 0.0%
0xffffffff81345678 | 2048 | 0 | 0.0%
适用场景
| 场景 | 说明 |
|---|---|
| 内核内存泄漏 | 观察 Net total bytes 是否持续增长 |
| 内存碎片 | 查看 Frag 列,高碎片率说明分配效率低 |
| 热点分配者 | --sort caller 找出最频繁的分配调用点 |
6.1.10 perf probe(动态追踪点)
perf probe 动态添加追踪点(基于 kprobes/uprobes),无需重新编译内核或应用程序即可追踪特定函数或代码位置。
基本用法
bash
# 添加内核追踪点(追踪 do_sys_open 函数,记录 filename 参数)
perf probe -a 'do_sys_open filename:string'
# 添加用户空间追踪点(追踪 libc 的 malloc 函数,记录 size 参数)
perf probe -x /lib/x86_64-linux-gnu/libc.so.6 'malloc size'
# 列出已添加的追踪点
perf probe -l
# 删除追踪点
perf probe -d do_sys_open
# 删除所有追踪点
perf probe -d '*'
# 查看可探测的函数
perf probe -F
常用选项
| 选项 | 说明 | 示例 |
|---|---|---|
| -a \ | 添加追踪点 | perf probe -a 'do_sys_open filename:string' |
| -d \ | 删除追踪点 | perf probe -d do_sys_open |
| -l | 列出所有追踪点 | perf probe -l |
| -F | 列出可探测的函数 | perf probe -F |
| -x \ | 指定用户态二进制 | perf probe -x /lib/libc.so.6 'malloc' |
| -L \ | 查看函数的可探测行 | perf probe -L do_sys_open |
| -V \ | 查看函数可用的变量 | perf probe -V do_sys_open |
配合 perf record 使用
bash
# 添加追踪点
perf probe -a 'do_sys_open filename:string'
# 使用该追踪点进行采样
perf record -e probe:do_sys_open -aR sleep 10
# 查看结果
perf script
实战示例:追踪特定函数的参数
bash
# 追踪内核 tcp_sendmsg 函数,记录 sk 和 size 参数
$ perf probe -a 'tcp_sendmsg sk size'
# 追踪用户态程序的特定函数
$ perf probe -x ./myserver 'handle_request request_id'
# 查看追踪结果
$ perf record -e probe:tcp_sendmsg -a -- sleep 5
$ perf script
注意事项
- 内核追踪点需要 root 权限
- 用户态追踪点需要二进制文件包含调试信息(
-g编译) - 追踪点使用完毕后应及时删除,避免影响系统性能
6.1.11 perf script / perf annotate(脚本输出与源码级分析)
perf script
将 perf.data 中的原始采样数据以文本形式输出,是生成火焰图的前置步骤。
bash
# 输出所有采样数据
perf script > perf.data.script
# 仅输出特定字段
perf script -f comm,pid,tid,time,ip,sym,dso
# 输出调用栈
perf script -g
perf script 常用选项:
| 选项 | 说明 | 示例 |
|---|---|---|
| -f \ | 指定输出字段 | perf script -f comm,pid,tid,time,ip,sym |
| -g | 输出调用栈 | perf script -g |
| -i \ | 指定输入文件 | perf script -i my.data |
| -F \ | 同 -f(旧版本) | --- |
perf script 输出示例:
myop 12345 [002] 12345.678901: 250000 cycles:
400b80 compute_hash (/home/user/program)
400a20 process_data (/home/user/program)
400950 main (/home/user/program)
7f1234 __libc_start_main (/lib/x86_64-linux-gnu/libc-2.31.so)
perf annotate
对指定函数进行源码级(或汇编级)性能标注,显示每条指令的采样占比。
bash
# 在 perf report 中按 'a' 键进入
# 或直接命令行使用
perf annotate <function_name>
# 指定输入文件
perf annotate -i perf.data compute_hash
perf annotate 输出示例:
Percent│ mov %eax,%edx
2.34│ add $0x1,%edx
45.67│ imul %ecx,%edx ← 热点指令
1.23│ mov %edx,(%rbx)
解读要点:Percent 列表示该指令被采样命中的占比。占比最高的指令即为函数内部的性能瓶颈。
6.1.12 perf bench(基准测试套件)
perf bench 提供内核子系统的基准测试套件,用于评估和对比系统性能。
子命令
| 命令 | 说明 | 示例 |
|---|---|---|
| perf bench sched | 调度器基准测试 | perf bench sched messaging |
| perf bench mem | 内存操作基准测试 | perf bench mem memcpy |
| perf bench futex | futex 基准测试 | perf bench futex wake |
| perf bench epoll | epoll 基准测试 | perf bench epoll wait |
实战示例
bash
# 内存拷贝基准测试
$ perf bench mem memcpy
# Copying 1MB bytes ...
# 3.452 GB/sec
# 调度器消息传递基准测试
$ perf bench sched messaging
# Running 'sched/messaging' benchmark:
# 20 sender and receiver processes per group
# 10 groups == 400 processes run
Total time: 0.123 [sec]
# futex 唤醒基准测试
$ perf bench futex wake
# Waking 100 threads...
# 100 threads woken in 0.001234 sec
适用场景
| 场景 | 推荐命令 |
|---|---|
| 评估内存带宽 | perf bench mem memcpy |
| 评估调度器性能 | perf bench sched messaging |
| 评估锁唤醒性能 | perf bench futex wake |
| 评估 epoll 性能 | perf bench epoll wait |
| 对比不同内核版本 | 在两个内核上运行相同 bench 对比结果 |
6.1.13 火焰图生成流程
火焰图(Flame Graph)是一种直观展示函数调用栈和时间消耗的可视化工具。X 轴表示采样占比(越宽表示占用越多),Y 轴表示调用栈深度。
#mermaid-svg-zulk2xZzE8VFT3GV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zulk2xZzE8VFT3GV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zulk2xZzE8VFT3GV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zulk2xZzE8VFT3GV .error-icon{fill:#552222;}#mermaid-svg-zulk2xZzE8VFT3GV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zulk2xZzE8VFT3GV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zulk2xZzE8VFT3GV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zulk2xZzE8VFT3GV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zulk2xZzE8VFT3GV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zulk2xZzE8VFT3GV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zulk2xZzE8VFT3GV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zulk2xZzE8VFT3GV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zulk2xZzE8VFT3GV .marker.cross{stroke:#333333;}#mermaid-svg-zulk2xZzE8VFT3GV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zulk2xZzE8VFT3GV p{margin:0;}#mermaid-svg-zulk2xZzE8VFT3GV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zulk2xZzE8VFT3GV .cluster-label text{fill:#333;}#mermaid-svg-zulk2xZzE8VFT3GV .cluster-label span{color:#333;}#mermaid-svg-zulk2xZzE8VFT3GV .cluster-label span p{background-color:transparent;}#mermaid-svg-zulk2xZzE8VFT3GV .label text,#mermaid-svg-zulk2xZzE8VFT3GV span{fill:#333;color:#333;}#mermaid-svg-zulk2xZzE8VFT3GV .node rect,#mermaid-svg-zulk2xZzE8VFT3GV .node circle,#mermaid-svg-zulk2xZzE8VFT3GV .node ellipse,#mermaid-svg-zulk2xZzE8VFT3GV .node polygon,#mermaid-svg-zulk2xZzE8VFT3GV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zulk2xZzE8VFT3GV .rough-node .label text,#mermaid-svg-zulk2xZzE8VFT3GV .node .label text,#mermaid-svg-zulk2xZzE8VFT3GV .image-shape .label,#mermaid-svg-zulk2xZzE8VFT3GV .icon-shape .label{text-anchor:middle;}#mermaid-svg-zulk2xZzE8VFT3GV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zulk2xZzE8VFT3GV .rough-node .label,#mermaid-svg-zulk2xZzE8VFT3GV .node .label,#mermaid-svg-zulk2xZzE8VFT3GV .image-shape .label,#mermaid-svg-zulk2xZzE8VFT3GV .icon-shape .label{text-align:center;}#mermaid-svg-zulk2xZzE8VFT3GV .node.clickable{cursor:pointer;}#mermaid-svg-zulk2xZzE8VFT3GV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zulk2xZzE8VFT3GV .arrowheadPath{fill:#333333;}#mermaid-svg-zulk2xZzE8VFT3GV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zulk2xZzE8VFT3GV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zulk2xZzE8VFT3GV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zulk2xZzE8VFT3GV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zulk2xZzE8VFT3GV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zulk2xZzE8VFT3GV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zulk2xZzE8VFT3GV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zulk2xZzE8VFT3GV .cluster text{fill:#333;}#mermaid-svg-zulk2xZzE8VFT3GV .cluster span{color:#333;}#mermaid-svg-zulk2xZzE8VFT3GV div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zulk2xZzE8VFT3GV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zulk2xZzE8VFT3GV rect.text{fill:none;stroke-width:0;}#mermaid-svg-zulk2xZzE8VFT3GV .icon-shape,#mermaid-svg-zulk2xZzE8VFT3GV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zulk2xZzE8VFT3GV .icon-shape p,#mermaid-svg-zulk2xZzE8VFT3GV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zulk2xZzE8VFT3GV .icon-shape .label rect,#mermaid-svg-zulk2xZzE8VFT3GV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zulk2xZzE8VFT3GV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zulk2xZzE8VFT3GV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zulk2xZzE8VFT3GV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} perf record
-F 99 -g
perf script
折叠调用栈
flamegraph.pl
生成SVG
flame.svg
浏览器打开
完整操作步骤
bash
# 1. 记录性能数据(采样频率99Hz,记录调用图)
perf record -F 99 -g ./program
# 2. 生成脚本输出
perf script > out.perf
# 3. 下载 FlameGraph 工具
git clone https://github.com/brendangregg/FlameGraph.git
# 4. 折叠调用栈并生成火焰图
FlameGraph/stackcollapse-perf.pl out.perf | FlameGraph/flamegraph.pl > flame.svg
# 5. 用浏览器打开 flame.svg 查看
火焰图解读要点
| 特征 | 含义 | 行动 |
|---|---|---|
| 色块越宽 | 该函数占用CPU时间越多(热点) | 优先优化 |
| 顶部宽平顶 | 该函数自身消耗大量CPU(非调用子函数) | 分析该函数内部逻辑 |
| 深层嵌套 | 调用链较深,可能存在过度抽象 | 考虑减少调用层次 |
| 颜色 | 随机分配,无特殊含义 | 不要过度解读颜色 |
| 尖塔形状 | 某个调用路径占比大 | 分析该路径是否必要 |
| 底部宽 | 该调用路径被频繁执行 | 关注整体路径 |
差分火焰图(对比优化前后)
bash
# 生成优化前的火焰图数据
perf record -F 99 -g -o before.data ./program_before
perf script -i before.data > before.perf
# 生成优化后的火焰图数据
perf record -F 99 -g -o after.data ./program_after
perf script -i after.data > after.perf
# 生成差分火焰图(红色=增加,蓝色=减少)
FlameGraph/stackcollapse-perf.pl before.perf > before.folded
FlameGraph/stackcollapse-perf.pl after.perf > after.folded
FlameGraph/difffolded.pl before.folded after.folded | FlameGraph/flamegraph.pl > diff.svg
6.2 gprof 性能剖析
gprof(GNU Profiler)是 GCC 自带的传统性能剖析工具,通过在编译时插入计数代码(-pg 选项),在程序运行时记录每个函数的调用次数和执行时间。与 perf 的采样方式不同,gprof 采用精确计数方式,每次函数调用都会被记录,因此结果完全精确(无统计误差),但代价是较大的运行时开销。
6.2.1 gprof 与 perf 的对比
| 特性 | gprof | perf |
|---|---|---|
| 原理 | 编译时插桩(计数器) | 硬件采样(PMU) |
| 需要重编译 | 是(必须加 -pg) |
否 |
| 性能开销 | 较大(5-15%) | 极小(<1%) |
| 精度 | 精确(每次调用都记录) | 统计性(采样) |
| 内核函数 | 不支持 | 支持 |
| 多线程 | 有限支持 | 完全支持 |
| 缓存/分支分析 | 不支持 | 支持 |
| 可视化 | 文本输出 | 火焰图、hotspot |
| 适用场景 | 小型程序精确分析 | 大型程序/生产环境 |
6.2.2 编译与运行(基本用法)
gprof 的使用流程分为三步:编译(插入计数代码)→ 运行(生成数据文件)→ 分析(解读报告)。
bash
# 步骤1:编译(插入性能分析代码,必须同时加 -pg 到编译和链接阶段)
gcc -pg -g -o program program.c
# 步骤2:运行程序(会在当前目录生成 gmon.out 文件)
./program
# 步骤3:确认 gmon.out 已生成
ls -la gmon.out
# 步骤4:分析
gprof program gmon.out > analysis.txt
注意事项
| 注意点 | 说明 |
|---|---|
-pg 必须同时出现在编译和链接命令中 |
仅在编译阶段加 -pg 不够,链接时也需要 |
| Makefile 中的配置 | 需要在 CFLAGS 和 LDFLAGS 中都加入 -pg |
| 程序必须正常退出 | 调用 exit() 或从 main 返回,gmon.out 才会生成 |
| SIGKILL 终止 | 如果程序通过 SIGKILL 终止,gmon.out 不会生成 |
| 静态库 | 静态库也需要用 -pg 编译才能被 gprof 追踪 |
| 动态库 | 动态库中的函数默认不被追踪(除非库也用 -pg 编译) |
6.2.3 gprof 命令分析(常用选项)
基本用法
bash
gprof [options] program gmon.out
常用选项速查表
| 选项 | 说明 | 典型用途 |
|---|---|---|
| -b | 简要输出,去除冗余说明文字 | 脚本处理输出 |
| -p | 仅显示 Flat Profile | 快速查看各函数耗时 |
| -q | 仅显示 Call Graph | 分析调用关系 |
| -z | 显示未被调用的函数 | 发现死代码 |
| -Z | 不显示未被调用的函数 | 精简输出 |
| -l | 按源码行计数 | 定位热点代码行(需 -g -pg 编译) |
| -A | 显示所有信息 | 完整分析 |
| -n | 显示每个函数的调用者和被调用者 | 调用链分析 |
| -N | 不显示调用者和被调用者 | 精简 Call Graph |
| -J | 不显示注释 | 精简输出 |
| -w \ | 设置输出宽度 | 控制输出格式 |
常用命令组合
bash
# 基本分析
gprof program gmon.out > analysis.txt
# 简要输出(去除冗余说明)
gprof -b program gmon.out
# 仅显示 flat profile
gprof -p program gmon.out
# 仅显示 call graph
gprof -q program gmon.out
# 显示未使用的函数(发现死代码)
gprof -z program gmon.out
# 不显示未使用的函数
gprof -Z program gmon.out
# 按行计数(需要 -g -pg 编译)
gprof -l program gmon.out
# 显示所有信息
gprof -A program gmon.out
6.2.4 实战示例(完整的编译→运行→分析流程)
以下示例展示一个完整的多文件项目使用 gprof 进行性能分析的全过程。
示例代码
c
// main.c
#include <stdio.h>
extern void process_data(int n);
int main(void)
{
process_data(1000);
return 0;
}
c
// compute.c
#include <math.h>
double compute_hash(int x)
{
double result = 0.0;
for (int i = 0; i < 1000; i++) {
result += sin(x * i) * cos(x + i);
}
return result;
}
void process_data(int n)
{
for (int i = 0; i < n; i++) {
compute_hash(i);
}
}
完整操作流程
bash
# 1. 编译(-pg 必须同时出现在编译和链接中)
gcc -pg -g -O2 -o program main.c compute.c -lm
# 2. 运行程序(生成 gmon.out)
./program
# 3. 确认数据文件已生成
ls -la gmon.out
# -rw-r--r-- 1 user user 736 Jan 15 10:30 gmon.out
# 4. 分析(完整输出)
gprof program gmon.out
# 5. 简要输出(推荐,去除冗余说明文字)
gprof -b program gmon.out
# 6. 仅看 Flat Profile
gprof -b -p program gmon.out
# 7. 仅看 Call Graph
gprof -b -q program gmon.out
# 8. 按行计数(定位热点代码行)
gprof -b -l program gmon.out
多文件 Makefile 配置
makefile
CC = gcc
CFLAGS = -pg -g -O2 -Wall
LDFLAGS = -pg -lm
SRCS = main.c compute.c
OBJS = $(SRCS:.c=.o)
TARGET = program
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $@ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET) gmon.out
profile: $(TARGET)
./$(TARGET)
gprof -b $(TARGET) gmon.out
.PHONY: all clean profile
6.2.5 输出解读
gprof 输出包含两部分:Flat Profile (平坦剖析)和 Call Graph(调用图)。
Flat Profile(平坦剖析)
Flat Profile 列出每个函数的执行时间统计,按耗时从高到低排序。
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
62.50 0.05 0.05 1000 0.05 0.05 compute_hash
25.00 0.07 0.02 100 0.20 0.70 process_data
12.50 0.08 0.01 1 10.00 80.00 main
Flat Profile 字段解读:
| 字段 | 说明 |
|---|---|
| % time | 该函数占总执行时间的百分比 |
| cumulative seconds | 累计执行时间(包含本函数及之前所有函数) |
| self seconds | 该函数自身消耗的时间(不含被调用函数) |
| calls | 该函数被调用的次数 |
| self ms/call | 每次调用自身消耗的平均时间 |
| total ms/call | 每次调用消耗的总平均时间(含被调用函数) |
| name | 函数名 |
Call Graph(调用图)
Call Graph 展示函数之间的调用关系和时间传播。每个函数条目由三部分组成:调用者(上方缩进行)、函数本身(中间行)、被调用者(下方缩进行)。
Call graph (explanation follows)
granularity: each sample print covers each 0.01 seconds.
index % time self children called name
0.01 0.07 1/1 main [2]
[1] 87.5 0.01 0.07 1 process_data [1]
0.05 0.00 1000/1000 compute_hash [3]
-----------------------------------------------
0.01 0.07 1/1 __libc_start_main [4]
[2] 100.0 0.01 0.07 1 main [2]
0.01 0.07 1/1 process_data [1]
-----------------------------------------------
0.05 0.00 1000/1000 process_data [1]
[3] 62.5 0.05 0.00 1000 compute_hash [3]
-----------------------------------------------
Call Graph 字段解读:
| 字段 | 说明 |
|---|---|
| index | 函数编号(对应 Call Graph 中的 [N]) |
| % time | 该函数及其所有子函数占总时间的百分比 |
| self | 该函数自身消耗的时间(不含子函数) |
| children | 该函数所有子函数消耗的时间总和 |
| called | 调用关系,格式为 本次调用次数/总调用次数,后面跟调用者名称 |
| name | 函数名 |
Call Graph 结构解读
以上述 process_data [1] 为例:
0.01 0.07 1/1 main [2] ← 调用者:main 调用了 process_data 1次
[1] 87.5 0.01 0.07 1 process_data [1] ← 函数本身:占总时间87.5%
0.05 0.00 1000/1000 compute_hash [3] ← 被调用者:process_data 调用了 compute_hash 1000次
Call Graph 特殊标记说明
| 标记 | 含义 |
|---|---|
<spontaneous> |
表示该函数的调用者未知(如 main 由运行时启动,无明确调用者) |
[N] |
函数编号,用于交叉引用 |
| 上方缩进行 | 调用该函数的父函数(caller) |
| 下方缩进行 | 该函数调用的子函数(callee) |
| 递归调用 | 函数名后显示 [N] 编号与自身相同,表示递归 |
- 分隔线 |
分隔不同函数的条目 |
递归调用的 Call Graph 表示
当函数存在递归调用时,gprof 会在 called 列中显示递归信息:
index % time self children called name
0.03 0.02 500/1500 factorial [3]
[3] 62.5 0.03 0.00 1500+1000 factorial [3]
0.02 0.00 1000/1500 factorial [3]
-----------------------------------------------
其中 1500+1000 表示:1500 次来自外部调用,另有 1000 次递归调用(自身调用自身)。
gprof 输出分析要点
| 分析目标 | 关注内容 |
|---|---|
| 找到最耗时函数 | Flat Profile 中 % time 最高的函数 |
| 分析调用开销 | 对比 self ms/call 和 total ms/call,差值大说明子函数开销大 |
| 发现过度调用 | calls 列数值异常大的函数 |
| 优化调用链 | Call Graph 中 children 值大的函数,优化其子函数 |
| 发现死代码 | 使用 gprof -z 查看从未被调用的函数 |
6.2.6 易错点与实践建议
| 易错点 | 说明 | 正确做法 |
|---|---|---|
只在编译时加 -pg |
链接时也需要 -pg |
编译和链接都加 -pg |
| 程序被 kill -9 终止 | gmon.out 不会生成 |
确保程序正常退出 |
| 优化级别过高 | -O3 的内联优化会使函数"消失" |
分析时加 -fno-inline |
| 多线程程序 | gprof 对多线程支持有限 | 多线程场景优先使用 perf |
| 动态库函数不可见 | 默认不追踪动态库中的函数 | 静态链接或使用 perf |
| gmon.out 被覆盖 | 每次运行都会覆盖 | 分析前备份或重命名 |
6.3 性能分析编译配合选项回顾
性能分析工具的正确使用离不开编译选项的配合。以下是与性能分析直接相关的编译选项总结。
6.3.1 帧指针保留(-fno-omit-frame-pointer)
| 选项 | 说明 |
|---|---|
| -fno-omit-frame-pointer | 保留帧指针寄存器(RBP/FP),perf 和 gprof 需要用于调用栈回溯 |
默认情况下,-O2 及以上优化级别会省略帧指针以释放一个寄存器供通用使用。但这会导致 perf 无法正确回溯调用栈。
bash
# 推荐:性能分析时使用
gcc -O2 -g -fno-omit-frame-pointer -o program program.c
注意: 如果无法重新编译(如分析第三方库),可使用
perf record --call-graph dwarf通过 DWARF 调试信息进行栈回溯(需要-g编译)。
6.3.2 调试信息(-g)
| 选项 | 说明 |
|---|---|
| -g | 生成调试信息,perf report 和 perf annotate 需要用于显示源码行号 |
| -g1 | 最小调试信息(仅回溯栈) |
| -g2 / -g | 默认级别(符号表 + 行号) |
| -g3 | 包含宏定义等额外信息 |
6.3.3 禁用干扰优化
| 选项 | 说明 | 影响 |
|---|---|---|
| -fno-inline | 禁用函数内联 | 使每个函数独立可见,便于分析,但严重影响性能 |
| -fno-optimize-sibling-calls | 禁用尾调用优化 | 保持调用栈完整,防止尾调用被优化为跳转 |
实践建议
- 初步定位问题时使用
-O2 -g -fno-omit-frame-pointer(保持优化但可分析) - 需要精确函数级分析时再加
-fno-inline - 生产环境性能分析不应禁用优化(会改变程序行为)
6.3.4 PGO 工作流(反馈优化)
PGO(Profile-Guided Optimization,反馈优化)利用程序实际运行的性能数据指导编译器优化,通常可带来 5-15% 的性能提升。
#mermaid-svg-NOp2xk3CsZECd53k{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NOp2xk3CsZECd53k .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NOp2xk3CsZECd53k .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NOp2xk3CsZECd53k .error-icon{fill:#552222;}#mermaid-svg-NOp2xk3CsZECd53k .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NOp2xk3CsZECd53k .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NOp2xk3CsZECd53k .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NOp2xk3CsZECd53k .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NOp2xk3CsZECd53k .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NOp2xk3CsZECd53k .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NOp2xk3CsZECd53k .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NOp2xk3CsZECd53k .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NOp2xk3CsZECd53k .marker.cross{stroke:#333333;}#mermaid-svg-NOp2xk3CsZECd53k svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NOp2xk3CsZECd53k p{margin:0;}#mermaid-svg-NOp2xk3CsZECd53k .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NOp2xk3CsZECd53k .cluster-label text{fill:#333;}#mermaid-svg-NOp2xk3CsZECd53k .cluster-label span{color:#333;}#mermaid-svg-NOp2xk3CsZECd53k .cluster-label span p{background-color:transparent;}#mermaid-svg-NOp2xk3CsZECd53k .label text,#mermaid-svg-NOp2xk3CsZECd53k span{fill:#333;color:#333;}#mermaid-svg-NOp2xk3CsZECd53k .node rect,#mermaid-svg-NOp2xk3CsZECd53k .node circle,#mermaid-svg-NOp2xk3CsZECd53k .node ellipse,#mermaid-svg-NOp2xk3CsZECd53k .node polygon,#mermaid-svg-NOp2xk3CsZECd53k .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NOp2xk3CsZECd53k .rough-node .label text,#mermaid-svg-NOp2xk3CsZECd53k .node .label text,#mermaid-svg-NOp2xk3CsZECd53k .image-shape .label,#mermaid-svg-NOp2xk3CsZECd53k .icon-shape .label{text-anchor:middle;}#mermaid-svg-NOp2xk3CsZECd53k .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NOp2xk3CsZECd53k .rough-node .label,#mermaid-svg-NOp2xk3CsZECd53k .node .label,#mermaid-svg-NOp2xk3CsZECd53k .image-shape .label,#mermaid-svg-NOp2xk3CsZECd53k .icon-shape .label{text-align:center;}#mermaid-svg-NOp2xk3CsZECd53k .node.clickable{cursor:pointer;}#mermaid-svg-NOp2xk3CsZECd53k .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NOp2xk3CsZECd53k .arrowheadPath{fill:#333333;}#mermaid-svg-NOp2xk3CsZECd53k .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NOp2xk3CsZECd53k .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NOp2xk3CsZECd53k .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NOp2xk3CsZECd53k .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NOp2xk3CsZECd53k .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NOp2xk3CsZECd53k .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NOp2xk3CsZECd53k .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NOp2xk3CsZECd53k .cluster text{fill:#333;}#mermaid-svg-NOp2xk3CsZECd53k .cluster span{color:#333;}#mermaid-svg-NOp2xk3CsZECd53k div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NOp2xk3CsZECd53k .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NOp2xk3CsZECd53k rect.text{fill:none;stroke-width:0;}#mermaid-svg-NOp2xk3CsZECd53k .icon-shape,#mermaid-svg-NOp2xk3CsZECd53k .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NOp2xk3CsZECd53k .icon-shape p,#mermaid-svg-NOp2xk3CsZECd53k .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NOp2xk3CsZECd53k .icon-shape .label rect,#mermaid-svg-NOp2xk3CsZECd53k .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NOp2xk3CsZECd53k .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NOp2xk3CsZECd53k .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NOp2xk3CsZECd53k :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 阶段1:插桩编译
gcc -fprofile-generate
运行程序
./program(收集数据)
生成 .gcda 文件
(性能计数数据)
阶段2:优化编译
gcc -fprofile-use
生成优化后的程序
(基于真实数据优化)
完整操作步骤
bash
# 阶段1:插桩编译(生成收集性能数据的代码)
gcc -fprofile-generate -O2 -o program program.c
# 运行程序(使用真实或代表性的输入,生成 .gcda 文件)
./program input_data
# 阶段2:使用收集的数据进行优化编译
gcc -fprofile-use -O2 -o program program.c
# 如果数据不完整或有冲突,使用校正选项
gcc -fprofile-use -fprofile-correction -O2 -o program program.c
PGO 相关编译选项汇总
| 选项 | 说明 |
|---|---|
| -fprofile-generate | 插入代码收集性能数据(编译和链接都需要) |
| -fprofile-use | 使用收集的性能数据进行优化(编译和链接都需要) |
| -fprofile-correction | 数据校正(处理多线程导致的计数不一致) |
| -fauto-profile | 使用外部性能分析数据(.afdo 文件,来自 perf) |
| -fprofile-generate=\ | 指定 .gcda 文件输出目录 |
| -fprofile-use=\ | 指定 .gcda 文件读取目录 |
auto-profile 工作流(使用 perf 数据指导优化)
bash
# 1. 使用 perf 采集数据
perf record -b ./program
# 2. 使用 create_gcov 工具转换(需要 autofdo 工具)
create_gcov --binary=./program --profile=perf.data --gcov=profile.afdo
# 3. 使用 afdo 文件优化编译
gcc -O2 -fauto-profile=profile.afdo -o program program.c
6.3.5 函数仪器化(-finstrument-functions)
| 选项 | 说明 |
|---|---|
| -finstrument-functions | 在每个函数入口和出口插入钩子调用 |
| -finstrument-functions-exclude-file-list=\ | 排除特定文件 |
| -finstrument-functions-exclude-function-list=\ | 排除特定函数 |
使用 -finstrument-functions 时,需要实现以下两个钩子函数:
c
void __cyg_profile_func_enter(void *func, void *caller);
void __cyg_profile_func_exit(void *func, void *caller);
这两个函数会在每个被仪器化函数的入口和出口被自动调用,参数分别是当前函数地址和调用者地址。可配合 addr2line 将地址转换为函数名,实现自定义的性能追踪。
6.3.6 性能分析编译选项总结表
| 选项 | 用途 | 配合工具 |
|---|---|---|
| -pg | 插入 gprof 计数代码 | gprof |
| -fno-omit-frame-pointer | 保留帧指针,支持栈回溯 | perf / gprof |
| -g | 生成调试信息(行号、符号) | perf report / perf annotate |
| -fno-inline | 禁用内联,使函数可见 | perf / gprof(调试阶段) |
| -fno-optimize-sibling-calls | 禁用尾调用优化,保持调用栈 | perf / gprof |
| -finstrument-functions | 函数入口/出口插桩 | 自定义追踪工具 |
| -fprofile-generate | PGO 第一阶段:收集数据 | PGO |
| -fprofile-use | PGO 第二阶段:使用数据优化 | PGO |
| -fauto-profile | 使用 perf 数据进行 PGO | autofdo |
| -fdebug-prefix-map==\ | 标准化调试路径 | 使性能数据可重现 |
| -gsplit-dwarf | 分离调试信息到 .dwo 文件 | 减小二进制大小 |
6.4 性能分析工具对比总结
本章介绍了 Linux 下两大类性能分析工具(perf 和 gprof)以及 PGO 反馈优化机制。以下从多个维度进行对比总结。
6.4.1 工具对比总表
| 对比维度 | perf | gprof | PGO |
|---|---|---|---|
| 工具类型 | 性能分析工具 | 性能剖析工具 | 编译优化机制 |
| 工作原理 | 硬件采样(PMU) | 编译时插桩(计数器) | 编译时插桩 + 反馈优化 |
| 需要重编译 | 否(建议加 -g -fno-omit-frame-pointer) |
是(必须加 -pg) |
是(两阶段编译) |
| 性能开销 | 极小(<1%) | 较大(5-15%) | 第一阶段有开销,最终产物无开销 |
| 精度 | 统计性(采样) | 精确(每次调用都记录) | N/A(不是分析工具) |
| 分析粒度 | 函数级 + 指令级(annotate) | 函数级 + 行级(-l) |
N/A |
| 内核函数 | 支持 | 不支持 | 不支持 |
| 多线程 | 完全支持 | 有限支持 | 支持(需 -fprofile-correction) |
| 缓存分析 | 支持(cache-misses 等事件) | 不支持 | 间接优化(基于热点路径) |
| 分支预测 | 支持(branch-misses) | 不支持 | 间接优化(基于分支概率) |
| 锁分析 | 支持(perf lock) | 不支持 | 不支持 |
| 调度分析 | 支持(perf sched) | 不支持 | 不支持 |
| 可视化 | 火焰图、hotspot | 文本输出 | N/A |
| 适用场景 | 生产环境、大型程序、系统级分析 | 开发环境、小型程序、精确函数分析 | 发布版本性能提升 |
| 使用难度 | 中等 | 简单 | 中等 |
| 典型性能提升 | N/A(分析工具) | N/A(分析工具) | 5-15% |
6.4.2 工具选择决策图
#mermaid-svg-mHc13nhlwSb1ZkEm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mHc13nhlwSb1ZkEm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mHc13nhlwSb1ZkEm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mHc13nhlwSb1ZkEm .error-icon{fill:#552222;}#mermaid-svg-mHc13nhlwSb1ZkEm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mHc13nhlwSb1ZkEm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mHc13nhlwSb1ZkEm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mHc13nhlwSb1ZkEm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mHc13nhlwSb1ZkEm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mHc13nhlwSb1ZkEm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mHc13nhlwSb1ZkEm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mHc13nhlwSb1ZkEm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mHc13nhlwSb1ZkEm .marker.cross{stroke:#333333;}#mermaid-svg-mHc13nhlwSb1ZkEm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mHc13nhlwSb1ZkEm p{margin:0;}#mermaid-svg-mHc13nhlwSb1ZkEm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mHc13nhlwSb1ZkEm .cluster-label text{fill:#333;}#mermaid-svg-mHc13nhlwSb1ZkEm .cluster-label span{color:#333;}#mermaid-svg-mHc13nhlwSb1ZkEm .cluster-label span p{background-color:transparent;}#mermaid-svg-mHc13nhlwSb1ZkEm .label text,#mermaid-svg-mHc13nhlwSb1ZkEm span{fill:#333;color:#333;}#mermaid-svg-mHc13nhlwSb1ZkEm .node rect,#mermaid-svg-mHc13nhlwSb1ZkEm .node circle,#mermaid-svg-mHc13nhlwSb1ZkEm .node ellipse,#mermaid-svg-mHc13nhlwSb1ZkEm .node polygon,#mermaid-svg-mHc13nhlwSb1ZkEm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mHc13nhlwSb1ZkEm .rough-node .label text,#mermaid-svg-mHc13nhlwSb1ZkEm .node .label text,#mermaid-svg-mHc13nhlwSb1ZkEm .image-shape .label,#mermaid-svg-mHc13nhlwSb1ZkEm .icon-shape .label{text-anchor:middle;}#mermaid-svg-mHc13nhlwSb1ZkEm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mHc13nhlwSb1ZkEm .rough-node .label,#mermaid-svg-mHc13nhlwSb1ZkEm .node .label,#mermaid-svg-mHc13nhlwSb1ZkEm .image-shape .label,#mermaid-svg-mHc13nhlwSb1ZkEm .icon-shape .label{text-align:center;}#mermaid-svg-mHc13nhlwSb1ZkEm .node.clickable{cursor:pointer;}#mermaid-svg-mHc13nhlwSb1ZkEm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mHc13nhlwSb1ZkEm .arrowheadPath{fill:#333333;}#mermaid-svg-mHc13nhlwSb1ZkEm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mHc13nhlwSb1ZkEm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mHc13nhlwSb1ZkEm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mHc13nhlwSb1ZkEm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mHc13nhlwSb1ZkEm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mHc13nhlwSb1ZkEm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mHc13nhlwSb1ZkEm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mHc13nhlwSb1ZkEm .cluster text{fill:#333;}#mermaid-svg-mHc13nhlwSb1ZkEm .cluster span{color:#333;}#mermaid-svg-mHc13nhlwSb1ZkEm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mHc13nhlwSb1ZkEm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mHc13nhlwSb1ZkEm rect.text{fill:none;stroke-width:0;}#mermaid-svg-mHc13nhlwSb1ZkEm .icon-shape,#mermaid-svg-mHc13nhlwSb1ZkEm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mHc13nhlwSb1ZkEm .icon-shape p,#mermaid-svg-mHc13nhlwSb1ZkEm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mHc13nhlwSb1ZkEm .icon-shape .label rect,#mermaid-svg-mHc13nhlwSb1ZkEm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mHc13nhlwSb1ZkEm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mHc13nhlwSb1ZkEm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mHc13nhlwSb1ZkEm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} CPU热点定位
能
不能
小型/精确分析
大型/生产环境
缓存性能
分支预测
调度延迟
锁竞争
False Sharing
整体优化
性能问题
问题类型?
能否重编译?
程序规模?
perf record + perf report
gprof -pg
perf record -g + 火焰图
perf stat -e cache-misses
perf record -e cache-misses
perf stat -e branch-misses
perf sched record + latency
perf lock record + report
perf c2c record + report
PGO: -fprofile-generate → -fprofile-use
6.4.3 性能分析工作流推荐
| 阶段 | 推荐工具 | 目的 |
|---|---|---|
| 初步评估 | perf stat | 获取 IPC、缓存命中率等宏观指标 |
| 热点定位 | perf record + perf report | 找出 CPU 占用最高的函数 |
| 调用链分析 | perf record -g + 火焰图 | 理解热点函数的调用路径 |
| 指令级分析 | perf annotate | 定位函数内部的热点指令 |
| 精确函数分析 | gprof(小项目) | 获取精确的调用次数和耗时 |
| 缓存优化 | perf stat -d / perf record -e cache-misses | 分析缓存行为 |
| 最终优化 | PGO | 基于真实负载数据优化编译 |
6.4.4 perf 子命令速查总表
| 子命令 | 功能 | 典型命令 | 适用场景 |
|---|---|---|---|
| perf stat | 计数器统计 | perf stat -e cycles,instructions ./prog |
快速评估 |
| perf record | 采样记录 | perf record -F 99 -g ./prog |
采集数据 |
| perf report | 报告分析 | perf report |
热点定位 |
| perf top | 实时监控 | perf top -p PID |
快速查看 |
| perf trace | 系统调用跟踪 | perf trace -s ./prog |
系统调用分析 |
| perf sched | 调度分析 | perf sched latency |
调度延迟 |
| perf lock | 锁分析 | perf lock report |
锁竞争 |
| perf c2c | 缓存一致性 | perf c2c report |
False Sharing |
| perf kmem | 内核内存 | perf kmem stat |
内核内存分配 |
| perf probe | 动态追踪点 | perf probe -a 'func arg' |
动态追踪 |
| perf script | 脚本输出 | perf script > out.perf |
火焰图前置 |
| perf annotate | 源码级分析 | perf annotate func |
指令级热点 |
| perf bench | 基准测试 | perf bench mem memcpy |
性能基准 |
6.4.5 gprof 选项速查总表
| 选项 | 功能 | 典型命令 | 适用场景 |
|---|---|---|---|
| (无选项) | 完整输出 | gprog prog gmon.out |
全面分析 |
| -b | 简要输出 | gprof -b prog gmon.out |
脚本处理 |
| -p | 仅 Flat Profile | gprof -p prog gmon.out |
快速看耗时 |
| -q | 仅 Call Graph | gprof -q prog gmon.out |
分析调用关系 |
| -z | 显示未调用函数 | gprof -z prog gmon.out |
发现死代码 |
| -l | 按行计数 | gprof -l prog gmon.out |
定位热点行 |
6.4.6 本章小结
本章系统介绍了 Linux 性能分析的完整工具链。perf 作为内核级性能分析工具,覆盖了从 CPU 热点到缓存行为、从调度延迟到锁竞争的全方位分析能力;gprof 作为传统的函数级剖析工具,在小型程序的精确分析中仍有不可替代的价值;PGO 则将性能分析数据反哺到编译优化中,实现"分析→优化"的闭环。三者的配合使用,构成了从"发现问题"到"解决问题"再到"预防问题"的完整性能工程体系。