Gab*_*erg 1732 c++ unix profiling
我有一个在Linux上运行的C++应用程序,我正在优化它.如何确定代码的哪些区域运行缓慢?
Mik*_*vey 1360
如果您的目标是使用分析器,请使用其中一个建议器.
但是,如果你很匆忙,并且你可以手动中断调试器下的程序,而主观速度很慢,那么可以通过一种简单的方法来查找性能问题.
只需暂停几次,每次都看一下调用堆栈.如果有一些代码浪费了一定比例的时间,20%或50%或其他什么,那就是你在每个样本的行为中捕获它的概率.所以这大约是您将看到它的样本的百分比.没有必要的教育猜测.如果您确实猜到了问题所在,这将证明或反驳它.
您可能会遇到不同大小的多个性能问题.如果你清除其中任何一个,剩下的将占用更大的百分比,并且在随后的传球中更容易发现.当在多个问题上复合时,这种放大效应可以导致真正大规模的加速因子.
警告:程序员往往对这种技术持怀疑态度,除非他们自己使用它.他们会说分析器会给你这些信息,但只有当他们对整个调用堆栈进行采样时才会这样,然后让你检查一组随机的样本.(摘要是失去洞察力的地方.)调用图不会给你相同的信息,因为
他们还会说它只适用于玩具程序,实际上它适用于任何程序,并且它似乎在更大的程序上更好地工作,因为它们往往有更多的问题要找.他们会说它有时会发现不是问题的东西,但只有在你看到一次之后才会这样.如果您在多个样本上看到问题,那就是真实的.
PS如果有一种方法可以在某个时间点收集线程池的调用堆栈样本,那么这也可以在多线程程序上完成,就像在Java中一样.
PPS作为一个粗略的概括,您在软件中拥有的抽象层越多,您就越有可能发现这是性能问题的原因(以及获得加速的机会).
补充:它可能不是很明显,但堆栈采样技术在递归的情况下同样有效.原因是通过删除指令节省的时间通过包含它的样本的分数来近似,而不管样本中可能出现的次数.
我经常听到的另一个反对意见是:" 它会在某个地方随机停止,它会错过真正的问题 ".这来自对现实问题的先验概念.性能问题的一个关键属性是他们无视期望.抽样告诉你一些问题,你的第一反应是难以置信.这很自然,但你可以确定它是否发现问题是真的,反之亦然.
补充:让我对其工作原理进行贝叶斯解释.假设有一些指令I(调用或其他)在调用堆栈中占用了一小部分f时间(因此成本太高).为简单起见,假设我们不知道是什么f,但假设它是0.1,0.2,0.3,...... 0.9,1.0,并且每种可能性的先验概率为0.1,因此所有这些成本同样可能先验.
然后假设我们只采集2个堆栈样本,并且我们看到I两个样本的指令,指定观察o=2/2.这给了我们对频率f的新估计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 | o=2/2)
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
Run Code Online (Sandbox Code Playgroud)
最后一栏说,例如,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 | o=2/2)
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
Run Code Online (Sandbox Code Playgroud)
现在它说P(f> = 0.5)是26%,高于先前假设的0.6%.所以贝叶斯允许我们更新我们对可能成本的估计I.如果数据量很小,它并不能准确地告诉我们成本是多少,只是它足够大,值得修复.
另一种看待它的方法叫做继承规则.如果你将硬币翻了2次,并且两次都出现了硬币,那么它对硬币的可能加权有什么影响呢?值得尊重的回答方式是说它是Beta分布,平均值(命中数+ 1)/(尝试次数+2)=(2 + 1)/(2 + 2)= 75%.
(关键是我们I不止一次看到.如果我们只看到一次,除了f> 0 之外,这并没有告诉我们多少.)
因此,即使是非常少量的样本也可以告诉我们很多关于它看到的指令成本的信息.(而且将看到他们的频率,平均成比例的成本.如果n采取试样,f是成本,那么I将出现在nf+/-sqrt(nf(1-f))样品.实施例,n=10,f=0.3,即3+/-1.4样品).
添加,以直观地感受测量和随机堆栈采样之间的差异:
现在有一些分析器可以对堆栈进行采样,即使是在挂钟时间,但是出现的是测量(或热路径,或热点,从中得到)一个"瓶颈"很容易隐藏).他们没有告诉你(他们很容易)你自己的实际样本.如果您的目标是找到瓶颈,那么您需要查看的数量平均为 2除以所需的时间.因此,如果需要30%的时间,平均2/.3 = 6.7个样本将显示它,并且20个样本显示它的机会是99.2%.
以下是检查测量和检查堆叠样本之间差异的袖口图示.瓶颈可能是这样的一个大块,或许多小块,它没有任何区别.
测量是水平的; 它告诉你特定例程所花费的时间.采样是垂直的.如果有任何方法可以避免整个程序在那一刻所做的事情,如果你在第二个样本上看到它,你就找到了瓶颈.这就是产生差异的原因 - 看到花费时间的全部原因,而不仅仅是花费多少.
Aja*_*jay 554
您可以使用Valgrind以下选项
valgrind --tool=callgrind ./(Your binary)
Run Code Online (Sandbox Code Playgroud)
它将生成一个名为的文件callgrind.out.x.然后,您可以使用kcachegrind工具来读取此文件.它会给你一个图形分析的结果,比如哪条线的成本是多少.
Naz*_*gob 338
我假设你正在使用GCC.标准解决方案是使用gprof进行分析.
确保-pg在分析之前添加到编译中:
cc -o myprog myprog.c utils.c -g -pg
Run Code Online (Sandbox Code Playgroud)
我还没有尝试过,但我听说过google-perftools的好消息.绝对值得一试.
相关问题在这里.
其他一些流行语如果gprof不适合你:Valgrind,Intel VTune,Sun DTrace.
Wil*_*ill 249
较新的内核(例如最新的Ubuntu内核)带有新的'perf'工具(apt-get install linux-tools)AKA perf_events.
重要的是,这些工具可以是系统分析而不仅仅是流程分析 - 它们可以显示线程,进程和内核之间的交互,让您了解进程之间的调度和I/O依赖性.

小智 72
我会使用Valgrind和Callgrind作为我的分析工具套件的基础.重要的是要知道Valgrind基本上是一个虚拟机:
(维基百科)Valgrind本质上是一个使用即时(JIT)编译技术的虚拟机,包括动态重新编译.原始程序中的任何内容都不会直接在主机处理器上运行.相反,Valgrind首先将程序转换为一种称为中间表示(IR)的临时,简单形式,这是一种处理器中立的,基于SSA的形式.在转换之后,在Valgrind将IR转换回机器代码并让主处理器运行它之前,工具(见下文)可以自由地在IR上进行任何转换.
Callgrind是一个构建于此的探查器.主要好处是您不必花费数小时来运行您的应用程序以获得可靠的结果.即使一次运行也足以获得坚如磐石,可靠的结果,因为Callgrind是一个非探测分析器.
建立在Valgrind上的另一个工具是Massif.我用它来分析堆内存使用情况.它很棒.它的作用是为你提供内存使用的快照 - 详细信息什么是内存百分比,以及世界卫生组织把它放在那里.此类信息可在应用程序运行的不同时间点获得.
Tõn*_*uel 63
valgrind --tool=callgrind没有一些选项,运行的答案并不完全.我们通常不想在Valgrind下描述10分钟的慢启动时间,并希望在执行某项任务时对我们的程序进行分析.
所以这就是我推荐的.首先运行程序:
valgrind --tool=callgrind --dump-instr=yes -v --instr-atstart=no ./binary > tmp
Run Code Online (Sandbox Code Playgroud)
现在,当它工作并且我们想要开始分析时,我们应该在另一个窗口中运行:
callgrind_control -i on
Run Code Online (Sandbox Code Playgroud)
这会打开分析.要关闭它并停止整个任务,我们可能会使用:
callgrind_control -k
Run Code Online (Sandbox Code Playgroud)
现在我们在当前目录中有一些名为callgrind.out.*的文件.要查看分析结果,请使用:
kcachegrind callgrind.out.*
Run Code Online (Sandbox Code Playgroud)
我建议在下一个窗口中单击"Self"列标题,否则显示"main()"是最耗时的任务."自我"显示每个功能本身需要花费多少时间,而不是与家属一起.
Rob*_*its 56
我在过去的几天里一直在使用Gprof,并且已经发现了三个重要的限制,其中一个我还没有在其他任何地方看到过记录:
除非您使用变通方法,否则它在多线程代码上无法正常工作
调用图被函数指针搞糊涂了.示例:我有一个函数调用multithread(),它使我能够在指定的数组上多线程化指定的函数(都作为参数传递).然而,Gprof将所有调用multithread()视为等效于计算儿童时间的目的.由于我传递的某些功能multithread()比其他功能要长得多,因此我的调用图大多没用.(对于那些想知道线程是否是问题的人:不,multithread()可以选择,并且在这种情况下,只在调用线程上顺序运行所有内容).
它在这里说"......呼叫数字是通过计数而非采样得出的.它们是完全准确的......".然而我发现我的调用图给了我5345859132 + 784984078作为我最常调用的函数的调用统计数据,其中第一个数字应该是直接调用,第二个递归调用(它们都来自自身).由于这暗示我有一个错误,我将长(64位)计数器放入代码并再次执行相同的操作.我的计数:5345859132直接和78094395406自我递归调用.那里有很多数字,所以我会指出我测量的递归调用是780亿,而Gprof是784m:100不同.两次运行都是单线程和未经优化的代码,一个是编译的,另一个是编译-g的-pg.
这是在64位Debian Lenny下运行的GNU Gprof(GNU Binutils for Debian)2.18.0.20080103,如果这有助于任何人.
Cir*_*四事件 36
C++分析技术综述:gprof vs valgrind vs perf vs gperftools
在这个答案中,我将使用几种不同的工具来分析一些非常简单的测试程序,以便具体比较这些工具的工作原理。
以下测试程序非常简单,并执行以下操作:
main电话fast和maybe_slow3 次,其中一个maybe_slow电话很慢
maybe_slow如果我们考虑对子函数的调用, 的慢调用时间要长 10 倍,并且在运行时占主导地位common。理想情况下,分析工具将能够将我们指向特定的慢速调用。
既fast与maybe_slow呼叫common,占大部分的程序执行的
程序界面为:
./main.out [n [seed]]
Run Code Online (Sandbox Code Playgroud)
并且程序O(n^2)总共执行循环。seed只是为了在不影响运行时获得不同的输出。
主文件
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
uint64_t __attribute__ ((noinline)) common(uint64_t n, uint64_t seed) {
for (uint64_t i = 0; i < n; ++i) {
seed = (seed * seed) - (3 * seed) + 1;
}
return seed;
}
uint64_t __attribute__ ((noinline)) fast(uint64_t n, uint64_t seed) {
uint64_t max = (n / 10) + 1;
for (uint64_t i = 0; i < max; ++i) {
seed = common(n, (seed * seed) - (3 * seed) + 1);
}
return seed;
}
uint64_t __attribute__ ((noinline)) maybe_slow(uint64_t n, uint64_t seed, int is_slow) {
uint64_t max = n;
if (is_slow) {
max *= 10;
}
for (uint64_t i = 0; i < max; ++i) {
seed = common(n, (seed * seed) - (3 * seed) + 1);
}
return seed;
}
int main(int argc, char **argv) {
uint64_t n, seed;
if (argc > 1) {
n = strtoll(argv[1], NULL, 0);
} else {
n = 1;
}
if (argc > 2) {
seed = strtoll(argv[2], NULL, 0);
} else {
seed = 0;
}
seed += maybe_slow(n, seed, 0);
seed += fast(n, seed);
seed += maybe_slow(n, seed, 1);
seed += fast(n, seed);
seed += maybe_slow(n, seed, 0);
seed += fast(n, seed);
printf("%" PRIX64 "\n", seed);
return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)
教授
gprof 需要使用仪器重新编译软件,并且它还与该仪器一起使用采样方法。因此,它在准确性(采样并不总是完全准确并且可以跳过函数)和执行速度减慢(检测和采样是相对较快的技术,不会大大减慢执行速度)之间取得平衡。
gprof 内置在 GCC/binutils 中,所以我们要做的就是使用-pg启用 gprof的选项进行编译。然后,我们使用 size CLI 参数正常运行程序,该参数会产生几秒钟的合理持续时间 ( 10000):
gcc -pg -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time ./main.out 10000
Run Code Online (Sandbox Code Playgroud)
出于教育原因,我们还将在不启用优化的情况下运行。请注意,这在实践中是无用的,因为您通常只关心优化优化程序的性能:
gcc -pg -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out 10000
Run Code Online (Sandbox Code Playgroud)
首先,time告诉我们有和没有的执行时间-pg相同,这很棒:没有减速!然而,我已经看到复杂软件的 2 到 3 倍减速,例如这张票所示。
因为我们用 编译-pg,所以运行程序会生成一个gmon.out包含分析数据的文件。
我们可以gprof2dot按照以下问题以图形方式观察该文件:Is it possible to get a graphics representation of gprof results?
sudo apt install graphviz
python3 -m pip install --user gprof2dot
gprof main.out > main.gprof
gprof2dot < main.gprof | dot -Tsvg -o output.svg
Run Code Online (Sandbox Code Playgroud)
在这里,该gprof工具读取gmon.out跟踪信息,并在 中生成人类可读的报告main.gprof,gprof2dot然后读取以生成图形。
gprof2dot 的来源是:https : //github.com/jrfonseca/gprof2dot
我们观察到以下-O0运行:
并为-O3运行:
该-O0输出几乎是不言自明的。例如,它显示 3 个maybe_slow调用及其子调用占用了总运行时间的 97.56%,尽管maybe_slow没有子项的自身执行占总执行时间的 0.00%,即几乎所有时间花在该函数上孩子打电话。
TODO:为什么输出中main缺少-O3,即使我可以bt在 GDB 中看到它?GProf 输出中缺少函数我认为这是因为 gprof 除了其编译的仪器之外还基于采样,而且-O3 main速度太快并且没有样本。
我选择 SVG 输出而不是 PNG,因为可以使用Ctrl+搜索 SVG,F并且文件大小可以小 10 倍左右。此外,对于复杂的软件,生成的图像的宽度和高度可能会非常大,有数万个像素,eog在这种情况下,GNOME 3.28.1 对于 PNG 会出错,而 SVG 会被我的浏览器自动打开。不过,gimp 2.8 运行良好,另请参阅:
但即便如此,您仍将大量拖动图像以找到您想要的内容,例如,请参阅从这张票中获取的“真实”软件示例中的此图像:
在所有这些细小的未排序的意大利面条行相互重叠的情况下,您能轻松找到最关键的调用堆栈吗?dot我确定可能有更好的选择,但我现在不想去那里。我们真正需要的是一个合适的专用查看器,但我还没有找到:
但是,您可以使用颜色图稍微缓解这些问题。比如,在之前的巨幅图像上,当我做出绿色在红色之后,最后是越来越深的蓝色的精彩演绎时,我终于找到了左边的关键路径。
或者,我们也可以观察gprof我们之前保存在的内置 binutils 工具的文本输出:
cat main.gprof
Run Code Online (Sandbox Code Playgroud)
默认情况下,这会产生一个非常详细的输出,解释输出数据的含义。由于我无法解释得更好,我会让你自己阅读。
一旦你理解了数据输出格式,你可以减少冗长,只显示没有教程的数据-b选项:
gprof -b main.out
Run Code Online (Sandbox Code Playgroud)
在我们的示例中,输出用于-O0:
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls s/call s/call name
100.35 3.67 3.67 123003 0.00 0.00 common
0.00 3.67 0.00 3 0.00 0.03 fast
0.00 3.67 0.00 3 0.00 1.19 maybe_slow
Call graph
granularity: each sample hit covers 2 byte(s) for 0.27% of 3.67 seconds
index % time self children called name
0.09 0.00 3003/123003 fast [4]
3.58 0.00 120000/123003 maybe_slow [3]
[1] 100.0 3.67 0.00 123003 common [1]
-----------------------------------------------
<spontaneous>
[2] 100.0 0.00 3.67 main [2]
0.00 3.58 3/3 maybe_slow [3]
0.00 0.09 3/3 fast [4]
-----------------------------------------------
0.00 3.58 3/3 main [2]
[3] 97.6 0.00 3.58 3 maybe_slow [3]
3.58 0.00 120000/123003 common [1]
-----------------------------------------------
0.00 0.09 3/3 main [2]
[4] 2.4 0.00 0.09 3 fast [4]
0.09 0.00 3003/123003 common [1]
-----------------------------------------------
Index by function name
[1] common [4] fast [3] maybe_slow
Run Code Online (Sandbox Code Playgroud)
和-O3:
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls us/call us/call name
100.52 1.84 1.84 123003 14.96 14.96 common
Call graph
granularity: each sample hit covers 2 byte(s) for 0.54% of 1.84 seconds
index % time self children called name
0.04 0.00 3003/123003 fast [3]
1.79 0.00 120000/123003 maybe_slow [2]
[1] 100.0 1.84 0.00 123003 common [1]
-----------------------------------------------
<spontaneous>
[2] 97.6 0.00 1.79 maybe_slow [2]
1.79 0.00 120000/123003 common [1]
-----------------------------------------------
<spontaneous>
[3] 2.4 0.00 0.04 fast [3]
0.04 0.00 3003/123003 common [1]
-----------------------------------------------
Index by function name
[1] common
Run Code Online (Sandbox Code Playgroud)
作为每个部分的非常快速的总结,例如:
0.00 3.58 3/3 main [2]
[3] 97.6 0.00 3.58 3 maybe_slow [3]
3.58 0.00 120000/123003 common [1]
Run Code Online (Sandbox Code Playgroud)
以左缩进的函数为中心 ( maybe_flow)。[3]是该函数的 ID。函数上方是调用者,下方是被调用者。
对于-O3,请参见此处,就像在图形输出中一样,maybe_slow并且fast没有已知的父级,这就是文档所说的<spontaneous>意思。
我不确定是否有一种很好的方法可以使用 gprof 进行逐行分析:`gprof` 在特定代码行上花费的时间
valgrind callgrind
valgrind 通过 valgrind 虚拟机运行程序。这使得分析非常准确,但它也产生了非常大的程序减速。我之前也提到过 kcachegrind:获取代码的图形函数调用图的工具
callgrind 是 valgrind 用于分析代码的工具,kcachegrind 是一个 KDE 程序,可以将 cachegrind 输出可视化。
首先,我们必须删除-pg标志以返回正常编译,否则运行实际上会失败Profiling timer expired,并且是的,这很常见,我这样做了,并且有一个堆栈溢出问题。
所以我们编译并运行:
sudo apt install kcachegrind valgrind
gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time valgrind --tool=callgrind valgrind --dump-instr=yes \
--collect-jumps=yes ./main.out 10000
Run Code Online (Sandbox Code Playgroud)
我启用--dump-instr=yes --collect-jumps=yes是因为这也转储了信息,使我们能够以相对较小的额外开销成本查看每条装配线的性能细分。
time马上,告诉我们程序执行时间为 29.5 秒,因此在此示例中我们的速度降低了大约 15 倍。显然,这种放缓将成为更大工作负载的严重限制。在此处提到的“真实世界软件示例”中,我观察到速度下降了 80 倍。
运行生成一个配置文件数据文件,callgrind.out.<pid>例如callgrind.out.8554在我的情况下。我们查看该文件:
kcachegrind callgrind.out.8554
Run Code Online (Sandbox Code Playgroud)
它显示了一个 GUI,其中包含类似于文本 gprof 输出的数据:
此外,如果我们进入右下角的“调用图”选项卡,我们会看到一个调用图,我们可以通过右键单击它来导出该调用图以获得以下带有不合理数量的白色边框的图像:-)
我认为fast没有显示在该图表上,因为 kcachegrind 必须简化了可视化,因为该调用占用的时间太少,这可能是您在真实程序中想要的行为。右键菜单有一些设置来控制何时剔除此类节点,但在快速尝试后我无法让它显示如此短的调用。如果我单击fast左侧窗口,它会显示带有 的调用图fast,因此实际上捕获了该堆栈。还没有人找到一种方法来显示完整的图形调用图:使 callgrind 显示 kcachegrind调用图中的所有函数调用
TODO 在复杂的 C++ 软件上,我看到了一些 type 的条目<cycle N>,例如<cycle 11>我期望函数名的地方,这是什么意思?我注意到有一个“循环检测”按钮可以打开和关闭它,但这是什么意思?
perf 从 linux-tools
perf似乎只使用 Linux 内核采样机制。这使得设置非常简单,但也不完全准确。
sudo apt install linux-tools
time perf record -g ./main.out 10000
Run Code Online (Sandbox Code Playgroud)
这为执行增加了 0.2 秒,所以我们在时间上很好,但在common使用键盘右箭头扩展节点后,我仍然没有看到太多兴趣:
Samples: 7K of event 'cycles:uppp', Event count (approx.): 6228527608
Children Self Command Shared Object Symbol
- 99.98% 99.88% main.out main.out [.] common
common
0.11% 0.11% main.out [kernel] [k] 0xffffffff8a6009e7
0.01% 0.01% main.out [kernel] [k] 0xffffffff8a600158
0.01% 0.00% main.out [unknown] [k] 0x0000000000000040
0.01% 0.00% main.out ld-2.27.so [.] _dl_sysdep_start
0.01% 0.00% main.out ld-2.27.so [.] dl_main
0.01% 0.00% main.out ld-2.27.so [.] mprotect
0.01% 0.00% main.out ld-2.27.so [.] _dl_map_object
0.01% 0.00% main.out ld-2.27.so [.] _xstat
0.00% 0.00% main.out ld-2.27.so [.] __GI___tunables_init
0.00% 0.00% main.out [unknown] [.] 0x2f3d4f4944555453
0.00% 0.00% main.out [unknown] [.] 0x00007fff3cfc57ac
0.00% 0.00% main.out ld-2.27.so [.] _start
Run Code Online (Sandbox Code Playgroud)
然后我尝试对-O0程序进行基准测试,看看它是否显示任何内容,直到现在,我才看到调用图:
Samples: 15K of event 'cycles:uppp', Event count (approx.): 12438962281
Children Self Command Shared Object Symbol
+ 99.99% 0.00% main.out [unknown] [.] 0x04be258d4c544155
+ 99.99% 0.00% main.out libc-2.27.so [.] __libc_start_main
- 99.99% 0.00% main.out main.out [.] main
- main
- 97.54% maybe_slow
common
- 2.45% fast
common
+ 99.96% 99.85% main.out main.out [.] common
+ 97.54% 0.03% main.out main.out [.] maybe_slow
+ 2.45% 0.00% main.out main.out [.] fast
0.11% 0.11% main.out [kernel] [k] 0xffffffff8a6009e7
0.00% 0.00% main.out [unknown] [k] 0x0000000000000040
0.00% 0.00% main.out ld-2.27.so [.] _dl_sysdep_start
0.00% 0.00% main.out ld-2.27.so [.] dl_main
0.00% 0.00% main.out ld-2.27.so [.] _dl_lookup_symbol_x
0.00% 0.00% main.out [kernel] [k] 0xffffffff8a600158
0.00% 0.00% main.out ld-2.27.so [.] mmap64
0.00% 0.00% main.out ld-2.27.so [.] _dl_map_object
0.00% 0.00% main.out ld-2.27.so [.] __GI___tunables_init
0.00% 0.00% main.out [unknown] [.] 0x552e53555f6e653d
0.00% 0.00% main.out [unknown] [.] 0x00007ffe1cf20fdb
0.00% 0.00% main.out ld-2.27.so [.] _start
Run Code Online (Sandbox Code Playgroud)
TODO:-O3执行过程中发生了什么?难道仅仅是maybe_slow和fast是太快了,并没有得到任何的样本?它是否适用-O3于需要更长执行时间的大型程序?我错过了一些 CLI 选项吗?我发现-F要控制赫兹的采样频率,但我将它调高到默认允许的最大值-F 39500(可以用 增加sudo),但我仍然看不到清晰的调用。
一个很酷的事情perf是来自 Brendan Gregg 的 FlameGraph 工具,它以一种非常简洁的方式显示调用堆栈时序,让您可以快速查看大调用。该工具可在:https://github.com/brendangregg/FlameGraph和还提到了他PERF教程:http://www.brendangregg.com/perf.html#FlameGraphs当我跑perf没有sudo我ERROR: No stack counts found这样的现在我将这样做sudo:
git clone https://github.com/brendangregg/FlameGraph
sudo perf record -F 99 -g -o perf_with_stack.data ./main.out 10000
sudo perf script -i perf_with_stack.data | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.svg
Run Code Online (Sandbox Code Playgroud)
但在这样一个简单程序的输出不是很容易理解,因为我们不能很容易地看到没有maybe_slow,也没有fast上图:
在一个更复杂的例子中,图表的含义变得很清楚:
TODO[unknown]在那个例子中有一个函数日志,为什么?
另一个可能值得的性能 GUI 界面包括:
Eclipse Trace Compass 插件:https : //www.eclipse.org/tracecompass/
但是,这是你必须首先将数据转换到通用跟踪格式,这是可以做到的缺点perf data --to-ctf,但它需要在构建时启用/有perf足够新,无论其是不是为PERF的情况下Ubuntu 18.04
https://github.com/KDAB/hotspot
这样做的缺点是似乎没有 Ubuntu 包,构建它需要 Qt 5.10,而 Ubuntu 18.04 是 Qt 5.9。
gperftools
以前称为“Google Performance Tools”,来源:https : //github.com/gperftools/gperftools基于示例。
首先安装 gperftools:
sudo apt install google-perftools
Run Code Online (Sandbox Code Playgroud)
然后,我们可以通过两种方式启用 gperftools CPU 分析器:在运行时或在构建时。
在运行时,我们必须通过 set LD_PRELOADto point to libprofiler.so,你可以找到它locate libprofiler.so,例如在我的系统上:
gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libprofiler.so \
CPUPROFILE=prof.out ./main.out 10000
Run Code Online (Sandbox Code Playgroud)
或者,我们可以在链接时构建库,LD_PRELOAD在运行时分配传递:
gcc -Wl,--no-as-needed,-lprofiler,--as-needed -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
CPUPROFILE=prof.out ./main.out 10000
Run Code Online (Sandbox Code Playgroud)
另请参阅:gperftools - 配置文件未转储
到目前为止,我发现查看此数据的最佳方法是使 pprof 输出与 kcachegrind 作为输入的格式相同(是的,Valgrind-project-viewer-tool)并使用 kcachegrind 查看:
google-pprof --callgrind main.out prof.out > callgrind.out
kcachegrind callgrind.out
Run Code Online (Sandbox Code Playgroud)
使用这两种方法中的任何一种运行后,我们都会得到一个prof.out配置文件数据文件作为输出。我们可以通过以下方式以图形方式将该文件视为 SVG:
google-pprof --web main.out prof.out
Run Code Online (Sandbox Code Playgroud)
它像其他工具一样提供了一个熟悉的调用图,但使用笨重的样本数单位而不是秒。
或者,我们也可以通过以下方式获取一些文本数据:
google-pprof --text main.out prof.out
Run Code Online (Sandbox Code Playgroud)
这使:
Using local file main.out.
Using local file prof.out.
Total: 187 samples
187 100.0% 100.0% 187 100.0% common
0 0.0% 100.0% 187 100.0% __libc_start_main
0 0.0% 100.0% 187 100.0% _start
0 0.0% 100.0% 4 2.1% fast
0 0.0% 100.0% 187 100.0% main
0 0.0% 100.0% 183 97.9% maybe_slow
Run Code Online (Sandbox Code Playgroud)
另请参阅:如何使用谷歌性能工具
使用原始perf_event_open系统调用检测您的代码
我认为这与使用的底层子系统相同perf,但您当然可以通过在编译时使用感兴趣的事件显式检测程序来获得更大的控制。
这对大多数人来说可能太硬核了,但它很有趣。Minimal runnable example at:
Ehs*_*san 23
使用Valgrind,callgrind和kcachegrind:
valgrind --tool=callgrind ./(Your binary)
Run Code Online (Sandbox Code Playgroud)
生成callgrind.out.x.使用kcachegrind读取它.
使用gprof(add -pg):
cc -o myprog myprog.c utils.c -g -pg
Run Code Online (Sandbox Code Playgroud)
(多线程,函数指针不太好)
使用google-perftools:
使用时间采样,显示I/O和CPU瓶颈.
英特尔VTune是最好的(免费用于教育目的).
其他: AMD Codeanalyst(自AMD CodeXL取代),OProfile,'perf'工具(apt-get install linux-tools)
另外值得一提的是
我使用过 HPCToolkit 和 VTune,它们在寻找帐篷中的长杆方面非常有效,并且不需要重新编译您的代码(除非您必须在 CMake 中使用 -g -O 或 RelWithDebInfo 类型构建以获得有意义的输出) . 我听说 TAU 的功能相似。
实际上有点惊讶没有多少人提到google/benchmark,虽然固定代码的特定区域有点麻烦,特别是如果代码库有点大,但是我发现这在与callgrind
恕我直言,识别造成瓶颈的部分是这里的关键。不过,我会首先尝试回答以下问题,然后根据该问题选择工具
valgrind结合callgrind和kcachegrind应该对上述几点提供一个不错的估计,一旦确定代码的某些部分存在问题,我建议做一个微基准测试 - 这google benchmark是一个很好的起点。
| 归档时间: |
|
| 查看次数: |
492274 次 |
| 最近记录: |