本文是在谷歌中使用的 CPU 分析器。
使用它有三个部分:将库链接到应用程序、运行代码 和 分析输出。
链接库
要将 CPU 分析器
安装到可执行文件中,请将 -lprofiler
添加到可执行文件的链接步骤中。(也可以在运行的时候使用 LD_PRELOAD
添加分析器,但不一定推荐这样做)
这不会开启 CPU 分析
,只是插入代码。因此,在开发过程中始终将 -lprofiler
链接到二进制文件是很实用的,Google 就是这么做的。(然而,由于任何用户都可以通过设置环境变量来开启分析器,所以并不一定建议将链接了分析器的二进制文件安装到正在运行的生产系统中)。
运行代码
对于给定的运行的可执行文件,有两种方法可以实际打开 CPU 分析
:
-
定义环境变量
CPUPROFILE
作为概要文件转储的文件名。例如,分析
/usr/local/netscape
:$ CPUPROFILE=/tmp/profile /usr/local/netscape # sh
% setenv CPUPROFILE /tmp/profile; /usr/local/netscape # csh 或者,在代码中,调用
ProfilerStart()
和ProfilerStop()
,将需要分析的代码括起来。ProfilerStart()
将概要文件名作为参数。
在 Linux2.6
及更高版本中,CPU 分析
自动分析所有线程。
在 Linux 2.4
中,CPU分析
只分析主线程(由于涉及itimers
及 threads
的 一个内核错误)。
分析器分析子进程:每个子进程都有一个以 CPUPROFILE
+子进程ID
组成的自己的概要文件。
出于安全原因,对于 setuid
程序,CPU 分析 不会写入文件,因此对于 setuid
CPU 分析 不可用。
通过环境变量控制行为
除了环境变量 CPUPROFILE
(决定概要文件写入位置),还有几个环境变量控制 CPU分析的性能。
PROFILEFREQUENCY=x CPU采样每秒中断多少次。
分析输出
pprof
是用于分析概要文件的脚本。它有许多输出模式,包括 文本模式
和 图形模式
。有些模式只给出原始数字(raw numbers),很像 gcc
的 -pg
输出,而另一些则以依赖图的形式显示数据。
pprof
需要安装 perl5
才能运行,如果需要任何图形输出则还需要为图形输出例程安装 dot
,并为 --gv
模式(如下所述)安装 gv
。
这里有一些调用 pprof
的方法,下面将更详细地描述这些内容。
% pprof "program" "profile"
每个方法生成一行
% pprof --gv "program" "profile"
生成带注释的调用图,并通过 "gv" 显示
% pprof --gv --focus=Mutex "program" "profile"
仅限于涉及与 "Mutex" 匹配的项的代码路径
% pprof --gv --focus=Mutex --ignore=string "program" "profile"
仅限于涉及与 "Mutex" 匹配与 "string" 不匹配的项的代码路径
% pprof --list=IBF_CheckDocid "program" "profile"
生成至少一个样本匹配到 --list= pattern 的所有例程的反汇编列表。
列表中的每一行都标注了扁平(flat)和累积(cumulative)的样本计数。
% pprof --disasm=IBF_CheckDocid "program" "profile"
生成至少一个样本匹配到 --disasm= pattern 的所有例程的反汇编列表。
列表中的每个 PC 值都标注了扁平(flat)和累积(cumulative)的样本计数。
节点信息
在 pprof
的各种图形模式中,输出的是一个标注了时间信息的调用图,如下图所示:
每个节点表示一个方法。有向边表示调用方与被调用方的关系。每个节点的格式如下:
Class Name (类名)
Method Name (方法名)
local (percentage) 本地(百分比)
cumulative (percentage) 累计(百分比)
最后一两行包含计时信息(分析是通过一个采样方法完成的,该方法每秒采集 100 个样本
,因此,输出中的一个 时间单位
对应于大约 10毫秒
的执行时间)。
“本地”(local
)时间是执行程序中直接包含的指令所花费的时间(以及内联到程序中的任何其他程序)。
“累积”(cumulative
)时间是“本地”时间与任何被调用者(即,子方法
)所花费时间的总和。如果累计时间与本地时间相同,则不打印。
例如,test_main_thread()
的计时信息表明,执行 test_main_thread()
中的代码花费了 155
个时间单位(大约 1.55秒
),在执行 test_main_thread()
及其被调用者-子方法(如,snprintf()
)时花费了 200个时间单位
。
节点的大小与本地计数成正比。节点中显示的百分比对应于计数除以程序的总运行时间(即,main()
的累计计数)。
边信息
从一个节点到另一个节点的边表示调用者到被调用者的关系。每条边都标有被调用方代表调用方所花费的时间。
例如,从 test_main_thread()
到 snprintf()
的边表示 test_main_thread()
中的 200 个样本中的 40 个是因为调用了snprintf()
。
注意⚠,test_main_thread()
有一条到 vsnprintf()
的边,尽管test_main_thread()
没有直接调用该函数。这是因为代码是用 -O2
编译的;概要文件反映了优化后
的控制流程。
元信息
显示的顶部应该包含一些元信息,如:
/tmp/profiler2_unittest
Total samples: 202
Focusing on: 202
Dropped nodes with <= 1 abs(samples)
Dropped edges with <= 0 samples
此部分包含程序的名称,以及分析运行期间收集的总样本数。
如果开启 --focus
选项(参见下面的 Focus
一节),则图例还包含在 focus
显示中显示的示例的数量。此外,一些不重要的节点和边被删除,以减少混乱。删除的节点和删除的边的特征也被显示在了图例中。
Focus 和 Ignore
你可以要求 pprof
生成一个专注于程序特定部分的显示。指定一个正则表达式。将保留调用图的任何部分,该部分位于包含至少一个与正则表达式匹配的节点的路径上。调用图的其余部分被丢弃。例如,您可以关注 profiler2_unittest
中的 vsnprintf() libc 调用
,如下所示:
% pprof --gv --focus=vsnprintf /tmp/profiler2_unittest test.prof
同样,你可以提供 --ignore
选项忽略匹配指定正则表达式的样本。例如,如果你对除了调用 snprintf()
之外的所有事情感兴趣:
% pprof --gv --ignore=snprintf /tmp/profiler2_unittest test.prof
pprof 选项
输出类型
--text
生成文本列表。这是目前的默认设置,因为它不需要访问 X 显示器,或 dot 或 gv。但是,如果安装了这些程序,您可能会对 --gv 输出感到满意。--gv
生成带注释的调用图,转换为postscript
,并通过gv
显示。--dot
以dot
格式生成带注释的调用图,并发送到stdout
。--ps
以Postscript
格式生成带注释的调用图,并发送到stdout
。--gif
以GIF
格式生成带注释的调用图,并发送到stdout
。--list=
输出名称与
匹配的例程的源代码列表。
列表中的每一行都标有扁平(flat
)和累积(cumulative
)的样本计数。
在存在内联调用的情况下,与内联代码相关联的样本倾向于被分配到跟随内联调用的位置的行。
通过使用--disasm
标志反汇编
例程,可以获得更精确的报告。--disasm=
生成与匹配的例程的反汇编,使用扁平(
flat
)和累积样本(cumulative
)的计数进行注释,并发送到stdout
。
报告的粒度
默认情况下,pprof
为每个方法生成一项,但是,你可以是用下列选项之一更改输出粒度。--files
选项似乎特别没用,最终可能会被移除。
--addresses
每个程序地址生成一个节点。--lines
每行生成一个节点。--functions
每个函数生成一个节点(这是默认值)。--files
每个源文件生成一个节点。
控制调用图的显示
为了减少输出显示中的混乱,丢弃了一些节点和边。以下选项控制此效果:
--nodecount=
此选项控制显示的节点数量。
首先对累计计数进行降序排序,然后取前 N 个节点,默认值是80
。--nodefraction=
此选项提供了另一种从显示中丢弃节点的机制。
如果节点的累计计数小于此选项的值乘以概要文件的总计数,则丢弃该节点。
默认值是0.005
;即,占总时间不到0.5%
的节点将被删除。
如果满足此条件或满足--nodecount
条件,则丢弃节点。--edgefraction=
此选项控制显示的边数。
首先,如果一条边的源节点或目标节点被丢弃,那么这条边被丢弃。
否则,如果沿边的样本计数小于此选项的值乘以概要文件的总计数,则丢弃边。
默认值是0.001
; 即,占总时间不到0.1%
的边将被丢弃。--focus=
此选项根据该选项提供的正则表达式控制显示图形的哪个区域。
对于调用图中的任何路径,我们将根据提供的正则表达式检查路径中的所有节点。
如果所有节点都不匹配,则从输出中丢弃该路径。--ignore=
此选项根据该选项提供的正则表达式控制显示图形的哪个区域。
对于调用图中的任何路径,我们将根据提供的正则表达式检查路径中的所有节点。
如果任何节点匹配,则从输出中丢弃该路径。
丢弃的一些边和节点导致显示中的一些计数不匹配。
例如,上面第一个图中 snprintf()
的累积计数是 41
。
然而,本地计数(1
)和沿输出边的计数(12+1+20+6
)加起来只有 40
。
附加说明
如果程序因为一个信号而退出,生成的概要文件将是不完整的,可能是完全空的。
由于上面描述的边缘丢弃启发方法,显示的图可能有断开的区域。
如果库中链接的程序没有使用足够的符号信息进行编译,则与库关联的所有示例都可能被记入到链接库之前的程序中找到的最后一个符号。这将人为地增加该符号的计数。
如果你在一台机器上运行该程序,并在另一台机器上对该程序进行概要分析,并且两台机器上的被共享的库是不同的,那么概要分析输出可能会令人困惑:属于共享库的示例可能被分配给任意程序。
如果程序进行 forks
,子进程也会被分析(因为它们继承相同的 CPUPROFILE
设置)。
每个进程都是单独分析的;为了区分子概要文件与父概要文件,所有子进程都有以自己的进程 id
+CPUPROFILE
命名的概要文件。
由于我们为解决可能的 gcc bug
而进行的一次黑客攻击,如果 CPUPROFILE
变量的第一个字符的 ascii
值 大于 127
,那么你的概要文件可能会以奇怪的名称命名。这应该非常罕见,但是如果您需要使用这样的名称,只需在文件名前加上 ./
:CPUPROFILE=./Ägypten
。