c++ - source - vs函数调用关系图



我如何分析在Linux中运行的C++代码? (7)

我有一个C ++应用程序,运行在Linux上,我正在优化。 我怎样才能确定我的代码的哪些区域运行缓慢?


Answer #1

使用Valgrind,callgrind和kcachegrind:

valgrind --tool=callgrind ./(Your binary)

生成callgrind.out.x。 使用kcachegrind阅读它。

使用gprof(加上-pg):

cc -o myprog myprog.c utils.c -g -pg 

(对于多线程,函数指针不太好)

使用谷歌perftools:

使用时间采样,揭示I / O和CPU瓶颈。

英特尔VTune是最好的(免费用于教育目的)。

其他: AMD Codeanalyst,OProfile,'perf'工具(apt-get install linux-tools)


Answer #2

如果您的目标是使用分析器,请使用其中一个建议的分析器。

但是,如果您很匆忙,而且在主调慢的情况下可以在调试器中手动中断程序,则有一种简单的方法可以找到性能问题。

只需暂停几次,每次看看调用堆栈。 如果有一些代码浪费了一定比例的时间,20%或50%,或者其他什么,那就是你在每个样本上的行为中可以捕捉到的概率。 所以这大致就是您将看到它的样本的百分比。 没有必要的猜测。 如果你猜猜问题是什么,这将证明或反驳它。

您可能有多个不同大小的性能问题。 如果你清理掉其中的任何一个,其余的将占用更大的比例,并且在随后的传球中更容易被发现。 这种放大效应在复合多个问题时会导致真正巨大的加速因子。

警告:程序员往往对这种技术持怀疑态度,除非他们自己使用它。 他们会说分析器会给你这些信息,但是只有当他们对整个调用堆栈进行采样,然后让你检查一组随机抽样时才是如此。 (摘要是洞察力丢失的地方。)调用图不会给你相同的信息,因为

  1. 他们没有在指导层面进行总结,而且
  2. 他们在递归的情况下给出了令人困惑的总结。

他们也会说它只适用于玩具程序,实际上它适用于任何程序,而且它似乎在更大的程序上运行得更好,因为它们往往会遇到更多问题。 他们会说它有时会发现不是问题的东西,但只有当你看到某种东西时才是如此。 如果您在多个样本上看到问题,则是真实的。

PS如果有一种方法可以在某个时间点收集线程池的调用栈样本,那么也可以在多线程程序中完成此操作,就像在Java中一样。

PPS简单来说,软件中抽象层次越多,发现性能问题的原因越多(有机会得到加速),就越有可能发生这种情况。

补充:它可能并不明显,但堆栈抽样技术在递归存在的情况下工作得很好。 原因是,通过删除指令可以节省的时间大概是包含它的样本的一小部分,而不管样本中可能发生多少次。

我经常听到的另一个反对意见是:“ 它会随机停止某个地方,它会错过真正的问题 ”。 这是由于事先有一个真正的问题是什么概念。 性能问题的一个重要特征是他们违背了期望。 抽样告诉你有什么问题,你的第一反应是不相信。 这很自然,但你可以肯定,如果它发现它是真实的问题,反之亦然。

增加:让我做一个贝叶斯解释它是如何工作的。 假设有一些指令I (呼叫或其他)在调用堆栈的一小部分时间(并因此花费那么多)。 为了简单起见,假设我们不知道f是什么,但假设它是0.1,0.2,0.3,... 0.9,1.0,并且这些可能性中的每一个的先验概率是0.1,所以所有这些成本是平等的可能是先验的。

那么假设我们只取两堆样本,并且我们在两个样本上看到指令I ,指定的观测值o=2/2 。 这给了我们对I的频率f的新估计,根据这个:

Prior                                    
P(f=x) x  P(o=2/2|f=x) P(o=2/2&&f=x)  P(o=2/2&&f >= x)  P(f >= x)

0.1    1     1             0.1          0.1            0.25974026
0.1    0.9   0.81          0.081        0.181          0.47012987
0.1    0.8   0.64          0.064        0.245          0.636363636
0.1    0.7   0.49          0.049        0.294          0.763636364
0.1    0.6   0.36          0.036        0.33           0.857142857
0.1    0.5   0.25          0.025        0.355          0.922077922
0.1    0.4   0.16          0.016        0.371          0.963636364
0.1    0.3   0.09          0.009        0.38           0.987012987
0.1    0.2   0.04          0.004        0.384          0.997402597
0.1    0.1   0.01          0.001        0.385          1

                  P(o=2/2) 0.385                

最后一列说,例如, f > = 0.5的概率是92%,高于前面的60%。

假设先前的假设是不同的。 假设我们假设P(f = 0.1)为.991(几乎可以肯定),并且所有其他可能性几乎不可能(0.001)。 换句话说,我们以前的确定性是I便宜。 然后我们得到:

Prior                                    
P(f=x) x  P(o=2/2|f=x) P(o=2/2&& f=x)  P(o=2/2&&f >= x)  P(f >= x)

0.001  1    1              0.001        0.001          0.072727273
0.001  0.9  0.81           0.00081      0.00181        0.131636364
0.001  0.8  0.64           0.00064      0.00245        0.178181818
0.001  0.7  0.49           0.00049      0.00294        0.213818182
0.001  0.6  0.36           0.00036      0.0033         0.24
0.001  0.5  0.25           0.00025      0.00355        0.258181818
0.001  0.4  0.16           0.00016      0.00371        0.269818182
0.001  0.3  0.09           0.00009      0.0038         0.276363636
0.001  0.2  0.04           0.00004      0.00384        0.279272727
0.991  0.1  0.01           0.00991      0.01375        1

                  P(o=2/2) 0.01375                

现在它说P(f> = 0.5)是26%,高于前面的0.6%。 所以贝叶斯允许我们更新我对可能成本的估计。 如果数据量很小,它不会准确告诉我们成本是多少,只是它足够大才值得修复。

另一种看待它的方式被称为继承规则 。 如果您将硬币翻转两次,并且两次都会出现,那么可以告诉您硬币的可能重量是什么? 推荐的答案是说它是一个Beta分布,平均值(命中数+ 1)/(尝试次数+2)=(2 + 1)/(2 + 2)= 75%。

(关键是我们不止一次看到我们,如果我们只看到它一次,那除了f > 0之外并没有多大意义)

因此,即使只有极少数的样本可以告诉我们很多关于它所看到的指令的成本。 (平均来看,它们的频率与它们的成本成正比,如果取n样本, f是成本,那么I将出现在nf+/-sqrt(nf(1-f))样本上。 , n=10f=0.3 ,即3+/-1.4样品。)

ADDED,为测量和随机堆栈采样之间的差异提供直观的感受:
即使在挂钟时间,现在还有一些探测器可以对栈进行采样,但是出现的是测量结果(或热路径或热点,“瓶颈”很容易隐藏起来)。 他们没有向你展示(他们很容易就可以)是他们自己的实际样本。 如果你的目标是找到瓶颈,那么你需要看到的数量平均为 2,除以所花费的时间。 因此,如果需要30%的时间,2 / .3 = 6.7个样本平均会显示出来,而20个样本显示的可能性为99.2%。

以下是检查测量结果与检查堆栈样本之间差异的非常规说明。 瓶颈可能是这样一个大块,或许多小块,它没有什么区别。

测量是水平的; 它会告诉你具体例程所花的时间是多少。 采样是垂直的。 如果有什么方法可以避免整个程序在那个时候正在做什么, 并且如果你在第二个样本上看到它 ,你就发现了瓶颈。 这就是造成这种差异的原因 - 看到时间消耗的全部原因,而不仅仅是多少。


Answer #3

您可以使用Valgrind和以下选项

valgrind --tool=callgrind ./(Your binary)

它会生成一个名为callgrind.out.x的文件。 然后可以使用kcachegrind工具读取该文件。 它会给你一个结果的图形分析,比如哪条线要花多少钱。


Answer #4

我会使用Valgrind和Callgrind作为我的分析工具套件的基础。 重要的是要知道Valgrind基本上是一台虚拟机:

(wikipedia)Valgrind本质上是一个使用即时(JIT)编译技术(包括动态重新编译)的虚拟机。 原始程序中的任何内容都不会直接在主处理器上运行。 相反,Valgrind首先将该程序翻译为临时更简单的称为中间表示(IR)的表单,该表单与处理器无关,基于SSA的表单形式。 转换之后,在Valgrind将IR转换回机器码并让主机处理器运行之前,一个工具(见下文)可以自由地在IR上进行任何转换。

Callgrind是一个建立在此基础上的分析器。 主要好处是您不必为了获得可靠的结果而运行几个小时的应用程序。 即使是一秒钟的运行也足以获得坚如磐石,可靠的结果,因为Callgrind是一个非探测分析器。

Valgrind的另一个工具是Massif。 我用它来分析堆内存使用情况。 它效果很好。 它的功能是为您提供内存使用的快照 - 详细信息WHAT包含了内存的百分比,并且WHO已将其放在那里。 这些信息在应用程序运行的不同时间点可用。


Answer #5

没有一些选项,运行valgrind --tool=callgrind的答案并不完整。 我们通常不希望在Valgrind中简介10分钟的启动时间,并且想要在执行某项任务时剖析我们的程序。

所以这是我的建议。 首先运行程序:

valgrind --tool=callgrind --dump-instr=yes -v --instr-atstart=no ./binary > tmp

现在,当它工作并且我们想开始分析时,我们应该在另一个窗口中运行:

callgrind_control -i on

这轮到分析。 要关闭它并停止整个任务,我们可以使用:

callgrind_control -k

现在我们在当前目录下有一些名为callgrind.out。*的文件。 要查看性能分析结果,请使用:

kcachegrind callgrind.out.*

我建议在下一个窗口中单击“Self”列标题,否则显示“main()”是最耗时的任务。 “自我”表明每个功能本身需要花费多少时间,而不是与受抚养人一起。


Answer #6

较新的内核(例如最新的Ubuntu内核)带有新的'perf'工具( apt-get install linux-tools )AKA perf_events

这些配备了经典的采样分析器( man-page )以及令人敬畏的timechart

重要的是,这些工具可以是系统分析 ,而不仅仅是进程分析 - 它们可以显示线程,进程和内核之间的交互,并让您了解进程之间的调度和I / O依赖关系。


Answer #7

这是对Nazgob的Gprof答案的回应 。

过去几天我一直在使用Gprof,并且已经发现了三个重要的限制,其中一个我还没有在其他地方见到过(还没有):

  1. 它在多线程代码上无法正常工作,除非您使用workaround

  2. 调用图被函数指针弄糊涂了。 示例:我有一个名为multithread()的函数,它使我能够在指定的数组上多线程指定的函数(都作为参数传递)。 但是,Gprof将所有对multithread()的调用视为等同于计算在儿童中花费的时间。 由于我传递给multithread()的一些函数比其他函数花费的时间长得多,所以我的调用图大多是无用的。 (对于那些想知道线程是否存在问题的人来说:no,multithread()可以选择,在这种情况下,只能在调用线程上按顺序运行)。

  3. here表示“......呼叫数字是通过计数而不是取样得出的,它们是完全准确的......”。 然而,我发现我的通话图给我5345859132 + 784984078作为我最常用的函数的调用统计,其中第一个数字应该是直接调用,第二个递归调用(全部来自它本身)。 由于这意味着我有一个错误,所以我在代码中放入了很长的(64位)计数器,并再次执行相同的操作。 我的计数:5345859132直接和78094395406自我递归调用。 这里有很多数字,所以我会指出我测量的递归调用是780亿,而Gprof则是784万:这是100个不同的因子。 两次运行都是单线程和未优化的代码,一次编译-g和另一个-pg。

这是在64位Debian Lenny下运行的GNU Gprof (GNU Binutils for Debian)2.18.0.20080103,如果这有助于任何人。





profiling