最近网上运行的一个程序出现了性能问题,但是通过分析程序源码(CodeReview),找不到问题的根源。所以只能借助强大的性能分析工具perf来查找问题所在。perf工具非常强大,但是本文不介绍perf工具的使用,而是介绍perf的实现原理。介绍perf使用的文章很多,介绍perf原理和实现的却很少。但是正因为perf很强大,它的实现也很复杂。本文只介绍其中一个功能:分析进程中函数调用的频率。下面先介绍一下如何使用perf分析进程中函数调用的频率。使用perf分析程序性能瓶颈在介绍perf的实现之前,我们先使用perf分析一个简单的程序,程序代码如下://sample.cvoidworkload1(){inti,c=0;对于(i=0;i<100000000;i++){c+=i*i;c-=我*100;c+=我*我*我/100;}}voidworkload2(){inti,c=0;对于(i=0;i<200000000;i++){c+=i*i;c-=我*100;c+=我*我*我/100;}}intmain(intargc,char*argv[]){workload1();工作量2();return0;}上面的程序很简单,我们创建了两个函数:workload1和workload2。从代码中可以看出,workload2的负载是workload1的两倍。现在我们使用perf来分析这个程序的性能瓶颈在哪里。首先,我们将程序编译成可执行文件。编译的时候记得加上-g参数,这样perf就可以拿到函数名了。$gccsample.c-g-osample使用perf的record命令来记录程序的运行情况。$sudoperfrecord-g./samplesleep10运行上述命令后,会生成一个perf.data文件,记录了示例程序运行时的采样数据。使用perf的report命令来分析程序的运行情况。$perfreport-g结果如下图所示:从上图可以看出,函数workload2(65%)的负载大约是函数workload1(35%)的两倍,基本符合我们的代码。perf的实现原理通过上面的例子,我们大概知道了如何使用perf来分析程序的性能瓶颈。接下来介绍一下perf的内部实现原理。我们想一想,如果我们要设计一个程序,统计程序中每个函数所占用的CPU时间,应该怎么设计呢?最简单的解决办法是:在每个函数的开始记录当前时间,然后在函数执行完后用当前时间减去函数开始执行的时间,得到函数的总执行时间。以下伪代码:voidfunc1(){...}voidfunc2(){...}intmain(intargc,char*argv[]){intstart_time,total_time;开始时间=现在();func1();total_time=now()-开始时间;printf("func1()花费了%d\n",total_time);开始时间=现在();func2();total_time=now()-开始时间;printf("func2()spent%d\n",total_time);}上面的方法虽然可以统计程序中每个函数的耗时,但是存在很多问题:代码侵入性强。由于每个函数都需要耗时记录,所以需要在调用函数前和函数调用后加入统计代码。统计函数非常耗时,不能反映函数的真实CPU使用率。例如,函数内部调用了导致进程休眠的系统调用(如sleep)。这个时候函数实际上并没有使用CPU,只是函数消耗的时间计入休眠时间。对性能影响很大。由于程序中所有函数都加入了统计代码,对性能的影响非常大。所以我们需要一个可以避免上述问题的系统:零代码入侵。能够真实反映函数的CPU占用情况。对性能影响不大。Perf就是为解决上述问题而诞生的。先介绍一下perf的原理。抽样为了减少对程序性能的影响,perf并没有在每个函数中加入统计代码,而是用一种统计方式来代替:抽样。采样的原理是:设置一个定时器,当定时器触发时,检查当前进程正在执行的函数,然后记录下来。如下图所示:如上图所示,每个cpu-clock是一个定时器的一个触发点。6个定时器触发点中,函数func1被命中3次,函数func2被命中1次,函数func3被命中2次。因此,我们可以推测函数func1的CPU使用率最高。排序如果程序有几万个函数,采样的数据可能会非常大。这时候需要对采样数据进行排序。perf为了对采样数据进行排序,使用了红黑树的数据结构,如下图:如上图所示,在perf采样的数据中,统计命中次数的函数有7个,而perf使用采样数据构建红黑树。根据红黑树的特性,最右边的节点就是命中最多的函数,这样就可以找到程序中CPU占用率最高的函数。总结由于perf的功能非常强大,本文只介绍perf的其中一个功能:CPU使用率统计功能。在下一篇文章中,我们将介绍perf的代码实现。Linux的创始人林纳斯曾经说过:Readthef**kingsourcecode。要真正了解一个系统,只能阅读它的源代码。
