perf

Posted by fjw on June 5, 2023

perf工具概述与性能问题定位指南

使用perf工具来诊断Linux系统下的性能瓶颈。perf是Linux内核自带的性能事件监控工具,它基于性能监控单元(PMU)和内核事件,能捕获CPU周期、缓存缺失、分支预测、系统调用等指标,非常适合多线程、并发和高负载场景的分析。

1. perf的基本安装与使用准备

  • 安装perf:在大多数现代Linux发行版(如Ubuntu、CentOS)中,perf已集成在内核中。如果使用Ubuntu,可以运行

    1
    
     sudo apt install linux-tools-common linux-tools-generic
    
  • 权限要求:普通用户可能受限,需要root权限或调整/proc/sys/kernel/perf_event_paranoid值为-1(允许所有用户访问)。

  • 常见命令

    • perf stat:快速统计性能计数器(如CPU周期、指令数)。
    • perf record:记录采样数据到perf.data文件。
    • perf report:分析记录的数据,支持交互式查看调用栈。
    • perf script:导出详细事件日志。
    • perf top:实时监控热点函数。

定位性能问题时,先用perf stat获取概览,再用perf record深入采样,最后用perf report可视化。

2. 如何用perf定位分析性能问题:通用步骤

性能分析的核心是“测量-分析-优化”的迭代循环。以下是标准流程:

  1. 识别问题场景
    • 运行你的程序(如C/C++多线程应用),观察症状:CPU利用率高但吞吐低?延迟异常?内存访问慢?
  2. 快速统计(perf stat)
    • 命令:perf stat -e cycles,instructions,cache-misses ./my_app
    • 这会输出如cycles(CPU周期)、instructions(执行指令数)、cache-misses(缓存缺失)等指标。
    • 分析:计算IPC(Instructions Per Cycle)= instructions / cycles。如果IPC < 1,可能是缓存或分支问题;如果cache-misses高,疑似内存瓶颈。
  3. 采样记录(perf record)
    • 命令:perf record -e cycles -g ./my_app(-g启用调用栈采样)。
    • 对于特定事件:-e cache-misses(缓存缺失)、-e syscalls:sys_enter_*(系统调用)、-e context-switches(上下文切换)。
    • 采样频率:默认1000Hz,可用-F 999调整。运行后生成perf.data。
  4. 分析报告(perf report)
    • 命令:perf report -g(显示调用栈)。
    • 交互界面:按比例排序函数热点(Overhead列)。展开栈帧查看具体代码行(需编译时加-g调试符号)。
    • 火焰图可视化:用perf script > out.perf; stackcollapse-perf.pl out.perf > out.folded; flamegraph.pl out.folded > flame.svg,浏览器查看热点。
  5. 迭代优化
    • 根据热点调整代码(如重构循环、减少锁争用)。
    • 验证:重复perf stat比较前后指标。

如果程序是多核的,用-a全系统采样;针对进程用-p PID。

3. 具体情况分析:false sharing cache miss

False sharing(伪共享)发生在多线程环境中,当多个线程访问同一缓存行(cache line,通常64字节)中的不同变量时。尽管变量逻辑独立,但由于缓存一致性协议(MESI),一个线程修改变量会使整个缓存行失效,导致其他线程的缓存miss(缺失),强制从内存重新加载。这会放大内存访问延迟,表现为CPU stall(停顿)增加,IPC下降。

原因

  • 典型场景:线程数组中相邻元素被不同线程读写(如struct中的字段紧挨着)。
  • 示例:两个线程分别更新struct { int a; int b; }的a和b,如果a和b在同一cache line,修改a会invalidate b的缓存。
  • 症状:高cache-misses、低L1/L2 hit rate、多线程下性能不随核心数线性增长。

方式

  1. 用perf stat -e cache-misses,cache-references ./my_app统计缺失率(misses / references > 10%疑似问题)。
  2. 深入:perf record -e cache-misses -g ./my_app,然后perf report查看热点。如果热点在无关变量访问,疑似false sharing。
  3. 确认:用perf c2c record ./my_app(cache-to-cache,需内核支持),然后perf c2c report显示cache line争用。看”Hitm”(Hit Modified)指标,高值表示remote cache hit(伪共享标志)。
  4. 优化:用padding填充变量到cache line边界(如加dummy char[56]),或用__cacheline_aligned宏。验证后miss率下降。

4. 具体情况分析:系统调用导致的性能问题

系统调用(syscall)涉及用户态到内核态切换,保存/恢复寄存器、TLB flush等,单次开销约数百纳秒。高频syscall(如频繁read/write)会累积成瓶颈,表现为CPU时间多花在内核(sys%高 in top命令)。

原因

  • 常见:I/O密集程序(如小块文件读写)、网络应用(频繁socket调用)、锁争用引起futex wait。
  • 症状:perf stat显示高context-switches或cycles in kernel mode。

方式

  1. 统计:perf stat -e syscalls:sys_enter_* ./my_app计数所有syscall。
  2. 具体类型:perf record -e ‘syscalls:sys_enter_read,syscalls:sys_enter_write’ -g ./my_app,report查看哪些函数触发。
  3. 追踪:perf trace ./my_app实时打印syscall序列。
  4. 优化:批量I/O(用mmap代替read)、减少锁(用无锁数据结构)。如果syscall是futex,检查线程同步。

5. 具体情况分析:上下文切换导致的性能问题

上下文切换(context switch)是内核调度器切换进程/线程时,保存/恢复CPU寄存器、栈、TLB的开销。 involuntary switches(非自愿,如时间片到期)表示争用;voluntary(如sleep)正常。

原因

  • 典型:线程数过多(>核心数)、高锁争用、I/O阻塞频繁,导致调度器频繁干预。
  • 症状:高cs(context switches) in vmstat,高迁移(migration)事件,程序响应慢但CPU idle。

方式

  1. 统计:perf stat -e context-switches,sched:sched_switch ./my_app计数切换次数。
  2. 记录:perf record -e context-switches -a -g,report查看切换发生在哪些函数(看sched_switch事件)。
  3. 调度分析:perf sched record ./my_app; perf sched latency显示延迟分布;perf sched map可视化线程调度。
  4. 优化:减少线程数、用线程池、避免忙等(spinlock过长)。如果高involuntary switches,检查负载均衡或亲和性(taskset绑定核心)。

总结与最佳实践

用perf分析时,从宏观(stat)到微观(record/report),结合火焰图可视化。性能问题是多因素的:false sharing常与多线程缓存相关,syscall与I/O,上下文切换与调度。实际中,结合strace(syscall追踪)增强诊断。可使用bpftrace扩展高级追踪。

probe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1. 添加探针(推荐用 libc 路径,兼容性更好)
sudo perf probe -x /lib/x86_64-linux-gnu/libc.so.6 malloc
sudo perf probe -x /lib/x86_64-linux-gnu/libc.so.6 'malloc%return $retval'

# 查看添加的探针
sudo perf probe -l

# 2. 记录(建议只针对目标进程,避免数据爆炸)
sudo perf record -e probe_libc:malloc,probe_libc:malloc__return -p $(pidof your_process) -- sleep 30

# 3. 用 perf script + awk 计算每条耗时(最常用后处理方式)
sudo perf script | awk '
/probe_libc:malloc.*$/     { start[$1,$2] = $4 }
/probe_libc:malloc__return/ {
    if (start[$1,$2]) {
        delta_ns = $4 - start[$1,$2];
        printf("tid=%-6s Δ=%-10.3f μs   %s\n", $2, delta_ns/1000, $0);
        delete start[$1,$2]
    }
}' | sort -k3 -n   # 按耗时排序看慢的