Perf:Linux下性能分析工具

本文阅读量 Posted by Kird on 2020-07-13

简介

Perf全名是Performance Event,是在Linux 2.6.31以后内建的系统效能分析工具,依靠perf,应用程式可以利用PMU (Performance Monitoring Unit), tracepoint和核心内部的特殊计数器(counter)来进行统计,另外还能同时分析运行中的核心程式码,从而更全面了解应用程式中的效能瓶颈。

perf基本原理是对目标进行取样,纪录特定的条件下所侦测的事件是否发生以及发生的次数。例如根据tick中断进行取样,即在tick中断内触发取样点,在取样点里判断行程(process)当时的context。假如一个行程90%的时间都花费在函式foo()上,那么90%的取样点都应该落在函式foo()的上下文中。

Perf 可取样的事件非常多,可以分析:

  • Hardware event,如cpu-cycles、instructions 、cache-misses、branch-misses

  • Software event,如page-faults、context-switches

  • Tracepoint event

知道了cpu-cycles、instructions 我们可以了解Instruction per cycle 是多少,进而判断程式码有没有好好利用CPU,cache-misses 可以晓得是否有善用Locality of reference ,branch-misses 多了是否导致严重的pipeline hazard ?另外Perf 还可以对函式进行采样,了解效能卡在哪边。

背景知识

硬件Cache

内存读写是很快的,但还是无法和处理器的指令执行速度相比。为了从内存中读取指令和数据,处理器需要等待,用处理器的时间来衡量,这种等待非常漫长。Cache 是一种 SRAM,它的读写速率非常快,能和处理器处理速度相匹配。因此将常用的数据保存在 cache 中,处理器便无须等待,从而提高性能。Cache 的尺寸一般都很小,充分利用 cache 是软件调优非常重要的部分。

CPU Cache相关的文章:

硬件特性之流水线,超标量体系结构,乱序执行

提高性能最有效的方式之一就是并行。处理器在硬件设计时也尽可能地并行,比如流水线,超标量体系结构以及乱序执行。

处理器处理一条指令需要分多个步骤完成,比如先取指令,然后完成运算,最后将计算结果输出到总线上。在处理器内部,这可以看作一个三级流水线。

指令从左边进入处理器,上图中的流水线有三级,一个时钟周期内可以同时处理三条指令,分别被流水线的不同部分处理。

超标量(superscalar)指一个时钟周期发射多条指令的流水线机器架构,比如 Intel 的 Pentium 处理器,内部有两个执行单元,在一个时钟周期内允许执行两条指令。

此外,在处理器内部,不同指令所需要的处理步骤和时钟周期是不同的,如果严格按照程序的执行顺序执行,那么就无法充分利用处理器的流水线。因此指令有可能被乱序执行。

上述三种并行技术对所执行的指令有一个基本要求,即相邻的指令相互没有依赖关系。假如某条指令需要依赖前面一条指令的执行结果数据,那么 pipeline 便失去作用,因为第二条指令必须等待第一条指令完成。因此好的软件必须尽量避免这种代码的生成。

分支指令对软件性能有比较大的影响。尤其是当处理器采用流水线设计之后,假设流水线有三级,当前进入流水的第一条指令为分支指令。假设处理器顺序读取指令,那么如果分支的结果是跳转到其他指令,那么被处理器流水线预取的后续两条指令都将被放弃,从而影响性能。为此,很多处理器都提供了分支预测功能,根据同一条指令的历史执行记录进行预测,读取最可能的下一条指令,而并非顺序读取指令。

分支预测对软件结构有一些要求,对于重复性的分支指令序列,分支预测硬件能得到较好的预测结果,而对于类似 switch case 一类的程序结构,则往往无法得到理想的预测结果。

上面介绍的几种处理器特性对软件的性能有很大的影响,然而依赖时钟进行定期采样的 profiler 模式无法揭示程序对这些处理器硬件特性的使用情况。处理器厂商针对这种情况,在硬件中加入了 PMU 单元,即 performance monitor unit。

PMU 允许软件针对某种硬件事件设置 counter,此后处理器便开始统计该事件的发生次数,当发生的次数超过 counter 内设置的值后,便产生中断。比如 cache miss 达到某个值后,PMU 便能产生相应的中断。

捕获这些中断,便可以考察程序对这些硬件特性的利用效率了。

Tracepoints

Tracepoint 是散落在内核源代码中的一些 hook,一旦使能,它们便可以在特定的代码被运行到时被触发,这一特性可以被各种 trace/debug 工具所使用。Perf 就是该特性的用户之一。

假如您想知道在应用程序运行期间,内核内存管理模块的行为,便可以利用潜伏在 slab 分配器中的 tracepoint。当内核运行到这些 tracepoint 时,便会通知 perf。

Perf 将 tracepoint 产生的事件记录下来,生成报告,通过分析这些报告,调优人员便可以了解程序运行时期内核的种种细节,对性能症状作出更准确的诊断。

Perf使用

前面提到的,Perf可以触发的时间大致分为3类,包含20 几种子工具集,通过 help 获取一手资料。

1
perf help top/mem/bench..

本文结合程序来进行perf使用的总结,用到的第一段程序代码perf_test.c如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int add(int i) {
i++;
return i;
}
int div(int i) {
i--;
return i;
}
int main() {
long int i = 0;
while(1) {
i++;
add(i);
div(i);
}
return 0;
}

上面程序的内容很容易发现是个死循环,这样的代码运行时,会很消耗系统性能,下面根据本程序来系统总结Perf的日常使用:

perf list

perf list 应该是大多数人安装完perf的第一个命令,能列出perf能触发的event,按照第一段介绍的,主要是三类event:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  alignment-faults                                   [Software event]
bpf-output [Software event]
context-switches OR cs [Software event]
cpu-clock [Software event]
cpu-migrations OR migrations [Software event]
...
power/energy-ram/ [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]
...
dma_fence:dma_fence_enable_signal [Tracepoint event]
dma_fence:dma_fence_init [Tracepoint event]
...

perf top

perf top类似Linux内建的top指令。它能够实时分析各个函式在某个event上的热点,找出拖慢系统的凶手,即使没有特定的程序要观察,你也可以直接下达perf top指令来观察是什么程序吃掉系统效能,导致系统异常变慢。

可以发现红色热点就出现了。右边第一列为各函式的符号,左边第一行是该符号引发的event在整个"监视域"中占的比例,我们称作该符号的热度,监视域指的是perf监控的所有符号,预设值包括系统所有程序、核心以及核心module的函式,左边第二行则为该符号所在的Shared Object 。若符号旁显示[.]表示其位于User mode,[k]则为kernel mode。

按下h可以呼叫help ,它会列出perf top的所有功能和对应按键。我们来试看看Annotate(注解),这功能可以进一步深入分析某个符号。通常光标所在的行事件回车两次也是"Annotate"。

下图是上面程序的div函数的Annotate:

如果你想要观察其他event ( 预设cycles ) 和指定取样频率( 预设每秒4000次) :

1
perf top -e cache-misses -c 4000

perf stat

该命令用在已知哪个程序需要优化,使用该命令对程序进行时间采样分析,找到可以优化的地方。如对perf_tes.c进行stat分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@k8s-master ~]# perf stat ./perf_test
^C./perf_test: Interrupt

Performance counter stats for './perf_test':

34,794.52 msec task-clock # 0.998 CPUs utilized
284 context-switches # 0.008 K/sec
506 cpu-migrations # 0.015 K/sec
113 page-faults # 0.003 K/sec
<not supported> cycles
<not supported> instructions
<not supported> branches
<not supported> branch-misses
34.859414299 seconds time elapsed
34.793114000 seconds user
0.001998000 seconds sys

不加-e选项指定你所感兴趣的特殊事件,perf stat将采集如下这些事件:

  • Task-clock-msecs:CPU 利用率,该值高,说明程序的多数时间花费在 CPU 计算上而非 IO。

  • Context-switches:进程切换次数,记录了程序运行过程中发生了多少次进程切换,频繁的进程切换是应该避免的。

  • Cache-misses:程序运行过程中总体的 cache 利用情况,如果该值过高,说明程序的 cache 利用不好

  • CPU-migrations:表示进程 t1 运行过程中发生了多少次 CPU 迁移,即被调度器从一个 CPU 转移到另外一个 CPU 上运行。

  • Cycles:处理器时钟,一条机器指令可能需要多个 cycles,

  • Instructions: 机器指令数目。

  • IPC:是 Instructions/Cycles 的比值,该值越大越好,说明程序充分利用了处理器的特性。

  • Cache-references: cache 命中的次数

  • Cache-misses: cache 失效的次数。

perf record / perf report

使用 top 和 stat 之后,您可能已经大致有数了。要进一步分析,便需要一些粒度更细的信息。比如说您已经断定目标程序计算量较大,也许是因为有些代码写的不够精简。那么面对长长的代码文件,究竟哪几行代码需要进一步修改呢?这便需要使用 perf record 记录单个函数级别的统计信息,并使用 perf report 来显示统计结果。

调优应该将注意力集中到百分比高的热点代码片段上,假如一段代码只占用整个程序运行时间的 0.1%,即使您将其优化到仅剩一条机器指令,恐怕也只能将整体的程序性能提高 0.1%。俗话说,好钢用在刀刃上。

使用record的时候,可能会出现的问题是取样频率太低,有些函数的信息没有没取样到而无法显示出来,可以通过 -F 选项调整取样频率,最大频率值为cat /proc/sys/kernel/perf_event_max_sample_rate

Perf 调优举例

上面通过死循环的代码,对常见的perf命令进行介绍,发现线上服务器的热点事件。下面将通过另一个例子,使用perf发现程序的问题并进行优化。

程序2,cpu_cache_miss.c:

1
2
3
4
5
6
7
8
static char array[10000][10000];
int main (void){
int i, j;
for (i = 0; i < 10000; i++)
for (j = 0; j < 10000; j++)
array[j][i]++;
return 0;
}
1
2
3
4
5
6
7
8
9
10
# perf stat --repeat 5 -e cache-misses,cache-references,instructions,cycles ./cpu_cache_miss

Performance counter stats for './cpu_cache_miss' (5 runs):

3,120,683 cache-misses # 3.056 % of all cache refs ( +- 0.20% )
102,114,204 cache-references ( +- 0.09% )
2,010,379,094 instructions # 0.83 insn per cycle ( +- 0.03% )
2,418,641,076 cycles ( +- 0.30% )

1.180222417 seconds time elapsed ( +- 1.53% )

如果我们善用一下存取的局部性,将i,j对调改成array[i][j]++,可参考与程序员相关的CPU缓存知识,优化后:

1
2
3
4
5
6
7
8
9
10
# perf stat --repeat 5 -e cache-misses,cache-references,instructions,cycles ./cpu_cache_miss_new

Performance counter stats for './cpu_cache_miss' (5 runs):

1,559,481 cache-misses # 83.462 % of all cache refs ( +- 0.18% )
1,868,502 cache-references ( +- 2.51% )
2,008,666,768 instructions # 1.36 insn per cycle ( +- 0.03% )
1,478,834,757 cycles ( +- 1.23% )

0.720040784 seconds time elapsed ( +- 3.21% )

可见IPC从0.83上升到了1.36。

火焰图

生成火焰图的步骤可以参考之前的总结文章:CPU火焰图

参考文章和扩展阅读



支付宝打赏 微信打赏

赞赏支持一下