
系统级性能优化通常包括两个阶段:性能剖析(performance profiling)和代码优化。性能剖析的目标是寻找性能瓶颈,查找引发性能问题的原因及热点代码。代码优化的目标是针对具体性能问题而优化代码或编译选项,以改善软件性能。本篇主要讲性能分析中常用的工具——perf。 perf是一款Linux性能分析工具。Linux性能计数器是一个新的基于内核的子系统,它提供一个性能分析框架,比如硬件(CPU、PMU(Performance Monitoring Unit))功能和软件(软件计数器、tracepoint)功能。通过perf,应用程序可以利用PMU、tracepoint和内核中的计数器来进行性能统计。它不但可以分析制定应用程序的性能问题(per thread),也可以用来分析内核的性能问题。 总之perf是一款很牛逼的综合性分析工具,大到系统全局性性能,再小到进程线程级别,甚至到函数及汇编级别。 5 ?% ]. T9 T: y. `& W8 N0 z6 [1 }; U 调优方向 可以从以下三种事件为调优方向:
. W D" r X" B3 c* _ perf 的使用
全局性概况:
全局细节:
特定功能分析:
最常用功能perf record,可以系统全局,也可以具体到某个进程,更甚具体到某一进程某一事件;可宏观,也可以很微观。
可视化工具perf timechart
" M+ P8 J( K7 ~9 p4 j+ V2 V 火焰图 火焰图(Flame Graph)是由Linux性能优化大师Brendan Gregg发明的,和所有其他的trace和profiling方法不同的是,Flame Graph以一个全局的视野来看待时间分布,它从底部往顶部,列出所有可能的调用栈。其他的呈现方法,一般只能列出单一的调用栈或者非层次化的时间分布。 ; m) |4 \' b* j1 m2 Y# f* C 以典型的分析CPU时间花费到哪个函数的on-cpu火焰图为例来展开。CPU火焰图中的每一个方框是一个函数,方框的长度,代表了它的执行时间,所以越宽的函数,执行越久。火焰图的楼层每高一层,就是更深一级的函数被调用,最顶层的函数,是叶子函数。 ![]() 火焰图的生成过程是:
脚本获取:git clone http://github.com/brendangregg/FlameGraph 下面通过实例来体验以下火焰图的生成过程: #include <pthread.h>1 C6 `" p* ], M1 A func_d() {, v4 w; O7 ?7 Q8 M3 ?% B int i; for(i=0;i<50000;i++);. e7 v6 e2 P( \6 K3 c/ K }$ I# Z! `3 w8 M$ B- d% @ func_a(): Q7 H! ^" H3 S) q3 @7 z {7 a4 Z' h) o. Z* A8 ? int i; for(i=0;i<100000;i++); func_d();! C/ k/ U5 E2 E; O9 h }# D% B" G5 v- B 8 C% i9 a, w9 q9 u. g1 a( y3 @* t func_b()) m# R4 b% H6 G { int i;$ L# p0 ^0 F0 s( C& d; { for(i=0;i<200000;i++);( c' ?" s4 e5 z6 j4 t }% i# F+ ~- M0 w 9 h) t% J: Z& w' _3 s& j* l5 H func_c()0 D% ^0 h9 Q: K4 g7 e1 e { int i;- R" |- X/ Q/ b0 I for(i=0;i<300000;i++);7 a! H4 }# }1 r; e$ | } - z! g5 K! W O; G/ \3 v4 C: X void* thread_fun(void* param)2 n6 R! {& k/ ~; n: C { while(1) { int i;. n5 n$ Y( k9 l5 j+ I for(i=0;i<100000;i++); & f: ]7 ] N2 Q, k4 F! t3 J& q _ func_a(); func_b();/ \. f B" t' E7 K. G1 {0 h func_c(); } } int main(void) { pthread_t tid1,tid2; int ret; 6 ]5 p* o1 I, u$ z5 B ret=pthread_create(&tid1,NULL,thread_fun,NULL);& }% V0 e! Q" {# ?3 E9 }- J if(ret==-1){ ... } ret=pthread_create(&tid2,NULL,thread_fun,NULL); ... if(pthread_join(tid1,NULL)!=0){+ m2 N( h+ j6 o, g+ w, Y% B. M ... }0 B- l( V- T* u: d% o) u if(pthread_join(tid2,NULL)!=0){+ L6 I, F* D. c8 z6 G* g .../ {& X! O+ b0 k! P/ K; h } return 0;$ |5 T( s' T& T1 o } 先用类似perf top分析出来CPU时间主要花费在哪里: $ gcc test.c -pthread $ ./a.out& $ sudo perf top ![]() perf top提示出来了fun_a()、fun_b()、fun_c(), fun_d(),thread_func()这些函数内部的代码是CPU消耗大户,但是它缺乏一个全局的视野,我们无法看出全局的调用栈,也弄不清楚这些函数之间的关系。火焰图则不然,我们用下面的命令可以生成火焰图(以root权限运行): 5 M8 s9 B0 [8 R6 B8 U" Q- K6 X $ perf record -F 99 -a -g -- sleep 60 $ perf script | ./stackcollapse-perf.pl > out.perf-folded $ ./flamegraph.pl out.perf-folded > perf-kernel.svg - `6 y" s! y# O, @4 N+ ^, J 上述程序捕获系统的行为60秒钟,最后调用flamegraph.pl生成一个火焰图perf-kernel.svg,用看图片的工具就可以打开这个svg。 ![]() 上述火焰图显示出了a.out中,thread_func()、func_a()、func_b()、fun_c()和func_d()的时间分布。 从上述火焰图可以看出,虽然thread_func()被两个线程调用,但是由于thread_func()之前的调用栈是一样的,所以2个线程的thread_func()调用是合并为同一个方框的。 ! L! _: m8 F. L/ H& i7 n 除了on-cpu的火焰图以外,off-cpu的火焰图,对于分析系统堵在IO、SWAP、取得锁方面的帮助很大,有利于分析系统在运行的时候究竟在等待什么,系统资源之间的彼此伊伴。 比如,下面的火焰图显示,nginx的吞吐能力上不来的很多程度原因在于sem_wait()等待信号量。 关于火焰图的更多细节和更多种火焰图各自的功能,可以访问: http://www.brendangregg.com/flamegraphs.html 8 n( x* u. w3 M! P+ O8 V8 o |