性能分析利器之perf浅析

文章目录
  1. 1. perf简介
  2. 2. perf的event
  3. 3. 安装
  4. 4. 背景
    1. 4.1 符号表
    2. 4.2 栈回溯(栈展开)
  5. 5. 使用
    1. 5.1. perf list
    2. 5.2. perf stat
    3. 5.3. perf top
    4. 5.4. perf record
    5. 5.5. perf可视化分析
  6. 6. 参考

作为服务器后台开发,不仅仅要写业务逻辑,后台意味着高并发,稳定性,当你写了很多逻辑,发现性能有问题的时候,也要学会性能分析,进行性能优化, 也许你会接触很多性能分析工具:valgrind,gperftools,gprof, oprofile, 有时间慢慢一一介绍,在学习perf的过程中,也学习和加深了很多之前的知识,本文抛砖引玉,希望能帮助大家了解一些性能分析更深的一些东西。

1. perf简介

perf是一款linux内置的性能分析工具,随着内核发布,也被称为perf_events, perf tools, PCL(Performance Counters for Linux), 发布于Linux kernel version 2.6.31, perf是怎么工作的呢?perf如何使用?本文是来介绍一下。

perf官方wiki的介绍是:Linux profiling with performance counters

Perf可以解决高级性能和故障排除,它可以分析(当然目前我也只是用来进行cpu性能分析):

  • 为什么内核消耗CPU高, 代码的位置在哪里?
  • 什么代码引起了CPU 2级cache未命中?
  • CPU是否消耗在内存I/O上?
  • 哪些代码分配内存,分配了多少?
  • 什么触发了TCP重传?
  • 某个内核函数是否正在被调用,调用频率多少?
  • 线程释放CPU的原因?

2. perf的event

它可以利用CPU performance counters(性能计数器),tracepoints, kprobes和uprobes来进行应用程序的性能分析。perf使用这些linux内核提供的tracing特性进行事件的采集和分析,perf使用的event主要有以下几种:

  • Hardware Events: CPU中的寄存器含有performance counters(性能计数器),用来统计 Hardware event,例如 cpu-cycles、instructions executed 、cache-misses、branch mispredicted等。这些event构成了应用程序profiling的基础。
  • Software Events: 基于内核计数器的低优先级events, 例如, CPU migrations(处理器迁移次数), minor faults(soft page faults), major faults(hard page faults).
  • Tracepoints: 是散落在内核源代码中的一些 hook,用来调用probe函数,开启后,它们便可以在特定的代码被运行到时被触发,这一特性可以被各种 trace/debug 工具所使用。Perf 就是该特性的用户之一。假如您想知道在应用程序运行期间,内核内存管理模块的行为,便可以利用潜伏在 slab 分配器中的 tracepoint。当内核运行到这些 tracepoint 时,便会通知 perf。
  • Dynamic Tracing: probe函数(探针or探测函数),kprobe(kernel probe)内核态探针,用来创建和管理内核代码中的探测点。Uprobes,user-probe,用户态探针,用来对用户态应用程序进行探测点的创建和管理,关于kprobeuprobe可参考对应的内核文档
    perf使用的event来源,如下图:来源:brendangregg

3. 安装

两种安装方式

  • 通过包管理进行安装,perf工具在 linux-tools-common工具包里,通过包管理软件安装的时候还需要依赖linux-tools-kernelversion包
  • 源码编译:找到对应内核版本的源码包,在tools/perf目录下进行编译

4. 背景

要想使用perf来分析应用程序,需要了解一些基础概念,例如符号表, frame pointer等信息

4.1 符号表

应用程序的符号表,用来将逻辑地址翻译成对应的函数和变量名,这样才能被程序员阅读和分析,没有符号表,profile的结果都是一些16进制的逻辑地址:
如下是perf report分析sshd进程的堆栈调用情况(来自brendangregg):

1
2
3
4
5
6
7
8
9
10
11
57.14%     sshd  libc-2.15.so        [.] connect           
|
--- connect
|
|--25.00%-- 0x7ff3c1cddf29
|
|--25.00%-- 0x7ff3bfe82761
| 0x7ff3bfe82b7c
|
|--25.00%-- 0x7ff3bfe82dfc
--25.00%-- [...]

对于通过软件包安装的程序,通常都会有dubug package(-dbgsym)即带有符号表信息的程序,如果是源码安装的就需要编译时开启debug选项。

通过使用openssh-server-dbgsym and libc6-dbgsym,sshd的perf report分析结果如下:

1
2
3
4
5
6
7
8
9
10
11
57.14%     sshd  libc-2.15.so        [.] __GI___connect_internal
|
--- __GI___connect_internal
|
|--25.00%-- add_one_listen_addr.isra.0
|
|--25.00%-- __nscd_get_mapping
| __nscd_get_map_ref
|
|--25.00%-- __nscd_open_socket
--25.00%-- [...]

4.2 栈回溯(栈展开)

我们经常在编译的时候会开启frame pointers优化选项,即优化掉函数栈帧rbp,省略掉frame pointer是编译器一个很烂的优化,它会破坏debug, 更烂的是省略frame pointer一般是编译器默认的优化选项。
没有frame pointer会使perf 无法获取完整的调用栈,如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void fun3(int a)
{
for(int i = 0; i < 1000000000; ++i)
++a;
}
void fun2(int a)
{
for(int i = 0; i < 100000000; ++i)
++a;
fun3(a);
}
void fun1(int a)
{
for(int i = 0; i < 100000000; ++i)
++a;
fun2(a);
}
int main()
{
fun1(1);
}

例如上面代码正常不开启优化的编译,进行profile是可以看到call graph的,如下:

如果编译时开启-fomit-frame-pointer(这里因为测试代码简单,直接开优化的话就优化没了),就无法获取到call graph

有三种方法可以修复这个问题,这里不做展开,这些stack walking techniques后面可以写一篇单独的文章:

  • using dwarf data to unwind the stack, 实际上很多profile工具:gperftools, valgrind都是依赖于libunwind,通过dwarf来进行stack trace的
  • using last branch record (LBR) if available (a processor feature)
  • returning the frame pointers

5. 使用

perf提供了一系列的命令来分析程序,如下:

sub command 功能说明
annotate 读取perf.data(由perf record生成)显示注释信息,如果被分析的进程含义debug符号信息,则会显示汇编和对应的源码,否则只显示汇编代码
archive 根据perf.data(由perf record生成)文件中的build-id将相关的目标文件打包,方便在其他机器分析
bench perf提供的基准套件的通用框架,可以对当前系统的调度,IPC,内存访问进行性能评估
buildid-cache 管理build-id,管理对于的bin文件
buildid-list 列出perf.data中的所以buildids
data 把perf.data文件转换成其他格式
diff 读取多个perf.data文件,并给出差异分析
evlist 列出perf.data中采集的事件列表
kmem 分析内核内存的使用
kvm 分析kvm虚拟机上的guest os
list 列出当前系统支持的所有事件名,可分为三类:硬件事件、软件事件,检查点
lock 分析内核中的锁信息,包括锁的争用情况,等待延迟等
record 对程序运行过程中的事件进行分析和记录,并写入perf.data
report 读取perf.data(由perf record生成) 并显示分析结果
sched 针对调度器子系统的分析工具。
script 读取perf.data(由perf record生成),生成trace记录,供其他分析工具使用
stat 对程序运行过程中的性能计数器进行统计
test perf对当前软硬件平台进行健全性测试,可用此工具测试当前的软硬件平台是否能支持perf的所有功能。
timechart 对record结果进行可视化分析输出,record命令需要加上timechart记录
top 对系统的性能进行分析,类型top命令,当然可以对单个进程进行分析
probe 用于定义动态检查点。
trace 类似于strace,跟踪目标的系统调用,但开销比strace小

perf的使用大体可以有三种方式:

  • Counting:统计的方式,统计事件发生的次数,这种方式不生成perf.data文件,例如perf stat, perf top
  • Sampling:采样的方式,采样事件,并写入到内核buffer中,并异步写入perf.data文件中,perf.data文件可以被perf report或者perf script 命令读取。
  • bpf programs on events(https://www.ibm.com/developerworks/cn/linux/l-lo-eBPF-history/index.html)

5.1. perf list

perf list命令可以列出当前perf可用的事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
cpu-cycles OR cycles                               [Hardware event]
instructions [Hardware event]
cache-references [Hardware event]
cache-misses [Hardware event]
branch-instructions OR branches [Hardware event]
branch-misses [Hardware event]
bus-cycles [Hardware event]
stalled-cycles-frontend OR idle-cycles-frontend [Hardware event]
stalled-cycles-backend OR idle-cycles-backend [Hardware event]
ref-cycles [Hardware event]

alignment-faults [Software event]
bpf-output [Software event]
context-switches OR cs [Software event]
cpu-clock [Software event]
cpu-migrations OR migrations [Software event]
dummy [Software event]
emulation-faults [Software event]
major-faults [Software event]
minor-faults [Software event]
page-faults OR faults [Software event]
task-clock [Software event]

msr/tsc/ [Kernel PMU event]

rNNN [Raw hardware event descriptor]
cpu/t1=v1[,t2=v2,t3 ...]/modifier [Raw hardware event descriptor]
(see 'man perf-list' on how to encode it)

mem:<addr>[/len][:access] [Hardware breakpoint]

这些事件可以分为三类(在文章开始介绍perf工作原理的时候也说了):Hardware Event, Software event, Tracepoint event.
每个具体事件的含义在perf_event_open的man page中有说明:

  • cpu-cycles:统计cpu周期数,cpu周期:指一条指令的操作时间。
  • instructions: 机器指令数目
  • cache-references: cache命中次数
  • cache-misses: cache失效次数
  • branch-instructions: 分支预测成功次数
  • branch-misses: 分支预测失败次数
  • alignment-faults: 统计内存对齐错误发生的次数, 当访问的非对齐的内存地址时,内核会进行处理,已保存不会发生问题,但会降低性能
  • context-switches: 上下文切换次数,
  • cpu-clock: cpu clock的统计,每个cpu都有一个高精度定时器
  • task-clock :cpu clock中有task运行的统计
  • cpu-migrations:进程运行过程中从一个cpu迁移到另一cpu的次数
  • page-faults: 页错误的统计
  • major-faults:页错误,内存页已经被swap到硬盘上,需要I/O换回
  • minor-faults :页错误,内存页在物理内存中,只是没有和逻辑页进行映射

5.2. perf stat

perf stat可以对程序运行过程中的性能计数器(包括Hardware,software counters)进行统计,分析程序的整体消耗情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$perf stat ls
Performance counter stats for 'ls':

2.164836 task-clock (msec) # 0.808 CPUs utilized
51 context-switches # 0.024 M/sec
4 cpu-migrations # 0.002 M/sec
333 page-faults # 0.154 M/sec
5506056 # 2.543 GHz
0 stalled-cycles-frontend # 0.00% frontend cycles idle
0 stalled-cycles-backend # 0.00% backend cycles idle
6100570 instructions # 1.11 insns per cycle
1298744 branches # 599.927 M/sec
18509 branch-misses # 1.43% of all branches

0.002679758 seconds time elapsed

具体各个字段的含义在perf list中已经列出了,这里分析一下重要的数据:

  • task-clock (msec): cpu处理task所消耗的时间,单位ms,0.808 CPUs utilized的表示cpu使用率为80.8% = 2.164836 / 2.679758. 该值越高代表程序是CPU bound而非IO bound 类型。
  • instructions:执行的指令条数, insns per cycle: 即IPC,每个cpu周期执行的指令条数,1.11 = 6100570(instructions) / 5506056(cycles),IPC比上面的CPU使用率更能说明CPU的使用情况,关于IPC有一篇brendangregg的文章,很好的说明CPU使用率不是一个很好的性能分析指标
  • stalled-cycles-frontend和stalled-cycles-backend表示CPU停滞统计,具体可参考

perf stat可以加上-e选项来设置自己关心的事件统计,具体参数可以通过上面的perf list来查看

5.3. perf top

perf top类似系统的命令top,可以实时的查看当前系统各个进程的各个函数的性能计数分析:$sudo perf top

perf top的和perf record的功能参数差不多,我们可以加上-e:来统计特定的event,-g: 开启call-graph, -p:分析特定的进程

平常我们用的更多的是perf record,因为它可以将profiler的结果输出到文件,然后通过perf report来进行分析,或者通过可视化工具进行分析和输出。所以下面着重介绍perf record

5.4. perf record

perf reord可以运行一个命令,但更多的是对已运行的进程进行性能分析,并将分析结果输出到perf.data(默认该文件名)中。不会像perf top一样实时输出分析结果。输出perf.data可以通过perf report进行分析,或者通过perf script输出结果给第三方输出可视化的视图。用前面的示例代码进行测试如下:

1
2
$perf record -g -F 99 ./a.out
$perf report

perf record几个总要参数说明一下:

  • -F: 事件采样的频率, 单位HZ, 更高的频率会获得更细的统计,但会带来更多的开销
  • -g: 进行堆栈追踪,生成调用关系图,等价于–call-graph, 默认情况下,-g等同于–call-graph fp,即通过frame pointer来进行堆栈追踪。 如果frame pointer被优化掉的话,可以通过dwarf, lbr进行堆栈追踪
  • sleep: 采样的时间

5.5. perf可视化分析

perf 提供了内置的可视化分析工具 perf timechart,例如:

1
2
$perf timechart record ./a.out
$perf timechart

perf timechart输出的是进程运行过程中系统调度的情况,无法对程序的具体代码段进行性能分析,但可以看出总结运行情况:running,idle,I/O等,

Brendangregg写了两款对perf采样结果进行可视化分析的开源工具:Flame Graphs和HeatMap. FlameGraphs即所谓的火焰图,是大家使用比较多的工具,能清晰的展示程序各个函数的性能消耗。HeatMap可以从采样数据中的延迟数据来进行消耗展示。
下面是对之前实例代码的采样数据perf.data进行FlameGraphs的绘制:

1
$perf script |./stackcollapse-perf.pl|./flamegraph.pl > fg_output.svg

6. 参考

Perf官方wiki
https://perf.wiki.kernel.org/index.php/Main_Page

Perf 维基百科
https://en.wikipedia.org/wiki/Perf_(Linux)

Perf – Linux下的系统性能调优工具,第 1 部分
https://www.ibm.com/developerworks/cn/linux/l-cn-perf1/index.html

Linux 效能分析工具: Perf
http://wiki.csie.ncku.edu.tw/embedded/perf-tutorial

Linux内核调试技术——kprobe使用与实现
https://blog.csdn.net/luckyapple1028/article/details/52972315

内核uprobes使用介绍
https://blog.csdn.net/badu_123/article/details/8302642

perf Examples
http://www.brendangregg.com/perf.html

火焰图的使用
http://www.brendangregg.com/flamegraphs.html
https://github.com/brendangregg/FlameGraph

页错误
https://yq.aliyun.com/articles/55820

http://cwndmiao.github.io/programming%20tools/2013/11/26/Dwarf/