如何分析在Linux上运行的C ++代码?

时间:2008-12-17 20:29:24

标签: c++ unix profiling

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

20 个答案:

答案 0 :(得分:1296)

答案 1 :(得分:520)

您可以将Valgrind与以下选项一起使用

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

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

答案 2 :(得分:321)

我假设您正在使用GCC。标准解决方案是使用gprof进行分析。

确保在分析之前将-pg添加到编译中:

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

我还没有尝试过,但我听说过有关google-perftools的好消息。绝对值得一试。

相关问题here

如果gprof无法为您完成工作,还有其他几个流行语:Valgrind,英特尔VTune,Sun DTrace

答案 3 :(得分:239)

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

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

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

Alt text

答案 4 :(得分:71)

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

  

(维基百科)Valgrind本质上是虚拟的   使用即时(JIT)的机器   编译技术,包括   动态重新编译。没什么   原始程序运行   直接在主处理器上。   相反,Valgrind首先翻译了   程序成一个临时的,简单的形式   称为中间代表   (IR),处理器中立,   基于SSA的表格。转换后,   一个工具(见下文)是免费的   它想要的任何转变   在Valgrind翻译之前,在IR上   IR回到机器代码并让   主处理器运行它。

Callgrind是一个构建于此的探查器。主要好处是您不必花费数小时来运行您的应用程序以获得可靠的结果。即使是一次性运行也足以获得坚如磐石,可靠的结果,因为Callgrind是一个非探测性分析器。

Valgrind的另一个工具是Massif。我用它来分析堆内存使用情况。它很棒。它的作用是为你提供内存使用的快照 - 详细信息什么是内存百分比,以及世界卫生组织把它放在那里。此类信息可在应用程序运行的不同时间点获得。

答案 5 :(得分:57)

如果没有一些选项,运行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()”是最耗时的任务。 “自我”显示每个功能本身需要花费多少时间,而不是与家属一起。

答案 6 :(得分:55)

这是对Nazgob's Gprof answer的回复。

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

  1. 除非您使用workaround

  2. ,否则它在多线程代码上无效
  3. 调用图被函数指针搞糊涂了。示例:我有一个名为multithread()的函数,它使我能够在指定的数组上多线程化指定的函数(两者都作为参数传递)。但是,Gprof将所有对multithread()的调用视为等效于计算儿童时间的目的。由于我传递给multithread()的某些函数比其他函数花费的时间长得多,因此我的调用图几乎没用。 (对于那些想知道线程是否是问题的人:不,multithread()可以选择,并且在这种情况下,只在调用线程上按顺序运行所有内容。)

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

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

答案 7 :(得分:18)

使用Valgrind,callgrind和kcachegrind:

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

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

使用gprof(添加-pg):

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

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

使用google-perftools:

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

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

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

答案 8 :(得分:18)

C ++分析技术调查:gprof,valgrind,perf,gperftools

在这个答案中,我将使用几种不同的工具来分析一些非常简单的测试程序,以便具体比较这些工具的工作方式。

以下测试程序非常简单,并且可以执行以下操作:

  • main呼叫fastmaybe_slow 3次,其中maybe_slow呼叫之一很慢

    maybe_slow的慢速调用长10倍,如果我们考虑调用子函数common,则慢速运行占主导地位。理想情况下,分析工具可以将我们指向特定的慢速通话。

  • fastmaybe_slow都调用common,这占程序执行的大部分

  • 程序界面为:

    ./main.out [n [seed]]
    

    ,该程序总共执行O(n^2)个循环。 seed只是为了获得不同的输出而不会影响运行时间。

main.c

#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;
}

gprof

gprof需要使用工具重新编译软件,并且它还与该工具一起使用采样方法。因此,它在准确性(采样并不总是完全准确并且可以跳过函数)与执行速度(仪表和采样是相对较快的技术,不会大大减慢执行速度)之间取得平衡。

gprof内置在GCC / binutils中,因此我们要做的就是使用-pg选项进行编译以启用gprof。然后,我们使用大小为CLI的参数正常运行程序,该参数会产生几秒钟的合理持续时间(10000):

gcc -pg -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time ./main.out 10000

出于教育原因,我们还将在未启用优化的情况下进行运行。请注意,这实际上是没有用的,因为您通常只关心优化已优化程序的性能:

gcc -pg -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out 10000

首先,time告诉我们,有-pg和没有-pg的执行时间是相同的,这很棒:不放慢速度!但是,我已经看到在复杂软件(例如为shown in this ticket

由于我们使用gmon.out进行了编译,因此运行该程序会生成一个包含分析数据的文件gprof2dot

我们可以按照Is it possible to get a graphical 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 图形化地观察该文件。
gprof

在这里,gmon.out工具读取main.gprof跟踪信息,并在gprof2dot中生成人类可读的报告,然后-O0读取该报告以生成图形。

gprof2dot的来源位于:https://github.com/jrfonseca/gprof2dot

对于-O3运行,我们观察到以下内容:

enter image description here

,然后进行-O0运行:

enter image description here

maybe_slow输出几乎是不言自明的。例如,它显示3个maybe_slow调用及其子调用占据了总运行时的97.56%,尽管main本身的执行没有子进程的执行占了总执行时间的0.00%,即几乎在该功能上花费的所有时间都花在了子调用上。

待办事项:即使我可以在GDB的-O3上看到bt的输出,为什么-O3也丢失了? Missing function from GProf output我认为这是因为gprof除了已编译的工具外,还基于采样,而main eog太快了,没有采样。

我选择SVG输出而不是PNG,因为可以使用Ctrl + F搜索SVG,并且文件大小可以小10倍左右。此外,对于复杂的软件,生成的图像的宽度和高度可能非常庞大,有成千上万个像素,而对于PNG,GNOME dot 3.28.1会在这种情况下出错,而SVG会由我的浏览器自动打开。 gimp 2.8效果很好,但另请参见:

但即使如此,您仍将图像拖拉很多以找到所需的内容,例如这张图片摘自this ticket的“真实”软件示例:

enter image description here

通过所有细小的未分类的意大利细面条线彼此重叠,您能轻松找到最关键的呼叫堆栈吗?我敢肯定,可能有更好的gprof选项,但我现在不想去那里。我们真正需要的是一个合适的专用查看器,但我还没有找到它:

但是,您可以使用颜色表来缓解这些问题。例如,在上一张大图上,当我做出精彩的推论时,我终于设法找到了左边的关键路径,即绿色紧随红色之后,最后是越来越深的蓝色。

或者,我们也可以观察cat main.gprof 内置binutils工具的文本输出,该工具先前保存在以下位置:

-b

默认情况下,这会产生非常冗长的输出,解释输出数据的含义。由于我无法解释得更好,因此我会让您自己阅读。

一旦您了解了数据输出格式,就可以减少冗长程度,而无需使用带有gprof -b main.out 选项的教程即可显示数据:

-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

-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

                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]

作为每个部分的快速摘要,例如:

maybe_flow

以左缩进([3])的函数为中心。 -O3是该功能的ID。该函数的上方是其调用方,而其下方是被调用方。

对于maybe_slow,请参见图形输出中的fast<spontaneous>没有已知的父级,这就是文档所说的-pg的含义

我不确定是否有很好的方法用gprof进行逐行分析:`gprof` time spent in particular lines of code

valgrind callgrind

valgrind通过valgrind虚拟机运行程序。这样可以使配置文件非常准确,但是也会导致程序运行速度大大降低。我之前在以下网址也提到过kcachegrind:Tools to get a pictorial function call graph of code

callgrind是valgrind的代码分析工具,kcachegrind是KDE程序,可以可视化cachegrind的输出。

首先,我们必须删除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 标志以返回正常编译,否则运行实际上会因Profiling timer expired而失败,是的,这是如此常见,以至于我发生了堆栈溢出问题。

所以我们编译并运行为:

--dump-instr=yes --collect-jumps=yes

我启用time是因为这还会转储信息,使我们能够以相对较小的开销开销查看每个装配线的性能故障。

一经发现,callgrind.out.<pid>告诉我们该程序花了29.5秒执行,因此在此示例中,速度降低了大约15倍。显然,这种减速将成为较大工作负载的严重限制。在“真实世界的软件示例” mentioned here中,我观察到速度降低了80倍。

该运行会生成名为callgrind.out.8554的配置文件数据文件,例如kcachegrind callgrind.out.8554 就我而言。我们通过以下方式查看该文件:

fast

其中显示了一个GUI,其中包含类似于文本gprof输出的数据:

enter image description here

此外,如果我们在右下角的“调用图”选项卡上,我们将看到一个调用图,可以通过右键单击该调用图来导出该图,以获取以下带有白色边框数量不合理的图像:-)

enter image description here

我认为fast没有显示在该图上,因为kcachegrind必须简化了可视化,因为该调用占用的时间太少,这很可能是您在实际程序上想要的行为。右键单击菜单有一些设置可以控制何时剔除此类节点,但是经过快速尝试后,我无法让它显示出如此短的调用。如果单击左侧窗口中的fast,它的确显示了带有<cycle N>的调用图,因此实际上已捕获了该堆栈。尚未有人找到一种显示完整图形调用图的方法:Make callgrind show all function calls in the kcachegrind callgraph

在复杂的C ++软件上的TODO,我看到了一些<cycle 11>类型的条目,例如perf在哪里需要函数名,这是什么意思?我注意到有一个“循环检测”按钮可以将其打开和关闭,但这是什么意思?

来自linux-tools

perf

sudo apt install linux-tools time perf record -g ./main.out 10000 似乎仅使用Linux内核采样机制。这使得设置非常简单,但也不够准确。

common

这使执行时间增加了0.2秒,因此我们时机不错,但是在使用键盘向右箭头扩展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 节点之后,我仍然没有看到太多兴趣:

-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 程序进行基准测试,看它是否显示任何内容,直到现在,我才终于看到调用图:

-O3

TODO:maybe_slow执行时发生了什么?仅仅是fast-O3太快而没有得到任何样本吗?它与-F在执行时间较长的大型程序上是否可以正常使用?我错过了一些CLI选项吗?我发现-F 39500左右以赫兹为单位控制采样频率,但是我将其设置为默认情况下sudo所允许的最大值(可以通过perf增加),但我仍然不这样做看不到清晰的通话。

关于perf的一件很酷的事情是Brendan Gregg的FlameGraph工具,该工具以非常整洁的方式显示了调用堆栈的时间,使您可以快速查看较大的调用。该工具可在以下位置找到:https://github.com/brendangregg/FlameGraph,他的性能教程中也提到了该工具:http://www.brendangregg.com/perf.html#FlameGraphs当我在没有sudo的情况下运行sudo时得到了ERROR: No stack counts found,因此我将使用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

maybe_slow

但是在这样一个简单的程序中,输出不是很容易理解,因为我们不能轻易在该图上看到fast[unknown]

enter image description here

在一个更复杂的示例中,可以清楚地看到图形的含义:

enter image description here

在该示例中,TODO有perf data --to-ctf个函数的日志,为什么?

另一个值得一试的perf GUI界面包括:

  • Eclipse Trace Compass插件:https://www.eclipse.org/tracecompass/

    但这有一个缺点,那就是您必须首先将数据转换为通用跟踪格式,可以使用perf完成,但是需要在构建时启用/必须使用sudo apt install google-perftools 新功能足够,在Ubuntu 18.04中perf都不是这种情况

  • 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:

LD_PRELOAD

然后,我们可以通过两种方式启用gperftools CPU分析器:在运行时或在构建时。

在运行时,我们必须将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 中找到它,例如在我的系统上:

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

google-pprof --callgrind main.out prof.out  > callgrind.out
kcachegrind callgrind.out

另请参阅:gperftools - profile file not dumped

到目前为止,查看此数据的最好方法是使pprof输出与kcachegrind用作输入的格式相同(是,Valgrind-project-viewer-tool),然后使用kcachegrind进行查看:

prof.out

使用这些方法之一运行后,我们将获得一个google-pprof --web main.out prof.out 配置文件数据文件作为输出。我们可以使用SVG以图形方式查看该文件:

google-pprof --text main.out prof.out

enter image description here

与其他工具一样,它提供了一个熟悉的调用图,但单位是采样数而不是秒。

或者,我们还可以通过以下方式获取一些文本数据:

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

给出:

perf_event_open

另请参阅:How to use google perf tools

使用原始的perf系统调用来插入代码

我认为这与{{1}}所使用的底层子系统相同,但是您当然可以通过在编译时显式地对感兴趣的事件进行检测来对程序进行更大的控制。

对于大多数人来说,这太难了,但这很有趣。最小的可运行示例,位于:Quick way to count number of instructions executed in a C program

在Ubuntu 18.04,gprof2dot 2019.11.30,valgrind 3.13.0,perf 4.15.18,Linux内核4.15.0,FLameGraph 1a0dc6985aad06e76857cf2a354bd5ba0c9ce96b,gperftools 2.5-2中进行了测试。

答案 9 :(得分:5)

对于单线程程序,您可以使用 igprof ,Ignominous Profiler:https://igprof.org/

它是一个采样分析器,沿着......很长的答案......由Mike Dunlavey回答,它将结果包装在一个可浏览的调用堆栈树中,注释每个函数花费的时间或内存,无论是累积的还是按功能的。

答案 10 :(得分:3)

这些是我用来加速代码的两种方法:

对于CPU绑定应用程序:

  1. 在DEBUG模式下使用分析器来识别代码的可疑部分
  2. 然后切换到RELEASE模式并注释掉代码中可疑部分(没有任何内容),直到看到性能发生变化为止。
  3. 对于I / O绑定的应用程序:

    1. 在RELEASE模式下使用分析器来识别代码的可疑部分。

    2. N.B。

      如果您没有探查器,请使用穷人的探查器。在调试应用程序时按下暂停。大多数开发人员套件将使用注释行号进入装配。从统计上来说,您可能会占用占用大部分CPU周期的区域。

      对于CPU,在 DEBUG 模式下进行性能分析的原因是因为如果您尝试在 RELEASE 模式下进行性能分析,编译器将减少数学,向量化循环和内联这些函数在汇编时往往会使代码变成无法映射的混乱。 不可映射的混乱意味着您的探查器无法清楚地识别出这么长时间的内容,因为程序集可能与优化下的源代码不对应。如果您需要 RELEASE 模式的性能(例如时序敏感),请根据需要禁用调试器功能以保持可用性能。

      对于I / O绑定,探查器仍然可以在 RELEASE 模式下识别I / O操作,因为I / O操作要么外部链接到共享库(大多数时候),要么在在最坏的情况下,将导致系统调用中断向量(分析器也可以轻松识别)。

答案 11 :(得分:2)

还值得一提的是

  1. HPCToolkit(http://hpctoolkit.org/)-开源,适用于并行程序,并具有用于以多种方式查看结果的GUI
  2. Intel VTune(https://software.intel.com/en-us/vtune)-如果您有intel编译器,那就非常好
  3. TAU(http://www.cs.uoregon.edu/research/tau/home.php

我使用了HPCToolkit和VTune,它们在查找帐篷中的长杆时非常有效,不需要重新编译代码(除非您必须使用-g -O或CMake中的RelWithDebInfo类型构建以获得)有意义的输出)。我听说TAU的功能类似。

答案 12 :(得分:1)

您可以使用iprof库:

https://gitlab.com/Neurochrom/iprof

https://github.com/Neurochrom/iprof

它是跨平台的,可让您不必实时测量应用程序的性能。您甚至可以将其与实时图形耦合。 完全免责声明:我是作者。

答案 13 :(得分:1)

使用调试软件 如何识别代码在哪里运行缓慢?

只要认为自己在运动中遇到障碍,就会降低速度

就像不必要的重新分配的循环,缓冲区溢出,搜索,内存泄漏等操作消耗了更多的执行能力,这会对代码性能产生不利影响, 在进行概要分析之前,请确保将-pg添加到编译中:

g++ your_prg.cpp -pgcc my_program.cpp -g -pg(根据您的编译器

还没有尝试过,但是我听说过有关google-perftools的好消息。绝对值得一试。

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

它将生成一个名为gmon.out或callgrind.out.x的文件。然后,您可以使用kcachegrind或调试器工具读取此文件。它将为您提供图形化的事物分析结果,例如哪些行花费多少。

我这么认为

答案 14 :(得分:0)

在工作中,我们有一个非常不错的工具,可以帮助我们监控调度方面的需求。这已经有用了很多次了。

它使用C ++,必须根据您的需求进行定制。不幸的是,我不能共享代码,只能共享概念。 您可以使用包含时间戳记和事件ID的“大” volatile缓冲区,可以在转储事后查看日志或在停止日志记录系统后将其转储(例如,将其转储到文件中)。

您检索具有所有数据的所谓大缓冲区,并通过一个小接口对其进行解析,并显示具有名称(上/下+值)的事件,就像示波器用颜色(在.hpp文件中配置)一样。

您可以自定义生成的事件数量,以仅关注您的需求。它基于每秒记录的事件数量,在帮助我们安排问题的同时,帮助我们消耗了所需的CPU数量。

您需要3个文件:

toolname.hpp // interface
toolname.cpp // code
tool_events_id.hpp // Events ID

概念是像这样在tool_events_id.hpp中定义事件:

// EVENT_NAME                         ID      BEGIN_END BG_COLOR NAME
#define SOCK_PDU_RECV_D               0x0301  //@D00301 BGEEAAAA # TX_PDU_Recv
#define SOCK_PDU_RECV_F               0x0302  //@F00301 BGEEAAAA # TX_PDU_Recv

您还可以在toolname.hpp中定义一些功能:

#define LOG_LEVEL_ERROR 0
#define LOG_LEVEL_WARN 1
// ...

void init(void);
void probe(id,payload);
// etc

只要您在代码中都可以使用:

toolname<LOG_LEVEL>::log(EVENT_NAME,VALUE);

probe函数使用几条组装线尽快获取时钟时间戳,然后在缓冲区中设置一个条目。我们还有一个原子增量,可以安全地找到存储日志事件的索引。 当然缓冲区是循环的。

希望这个想法不会因缺少示例代码而被混淆。

答案 15 :(得分:0)

您可以使用loguru之类的日志记录框架,因为它包含时间戳记和总正常运行时间,可以很好地用于性能分析:

答案 16 :(得分:0)

正如没有人提到Arm MAP一样,我会添加它,因为我个人已经成功地使用Map来分析C ++科学程序。

Arm MAP是用于并行,多线程或单线程C,C ++,Fortran和F90代码的探查器。它提供了对源代码行的深入分析和瓶颈。与大多数探查器不同,该探查器旨在探查pthreads,OpenMP或MPI的并行和线程代码。

MAP是商业软件。

答案 17 :(得分:0)

实际上,关于google/benchmark的提及并不多,虽然固定代码的特定区域有点麻烦,特别是在代码库较大的情况下,但是我发现这在使用时确实很有帮助与callgrind

结合使用

恕我直言,找出导致瓶颈的部分是这里的关键。不过,我会先尝试回答以下问题,然后根据该问题选择工具

  1. 我的算法正确吗?
  2. 有没有被证明是瓶颈的锁?
  3. 是否有特定的代码部分被证明是罪魁祸首?
  4. 如何处理和优化IO?

valgrindcallrindkcachegrind的组合应该对以上几点提供一个不错的估计,一旦确定某些代码段存在问题,我建议做一个微型基准测试google benchmark是一个很好的起点。

答案 18 :(得分:0)

在编译和链接代码并运行可执行文件时,请使用-pg标志。执行此程序时,分析数据收集在文件a.out中。
有两种不同类型的配置文件

1-平面轮廓:
 通过运行命令gprog --flat-profile a.out,您得到了以下数据
 -该功能花费了总时间的百分比,
 -一个功能花费了多少秒-包括但不包括对子功能的调用,
 -通话次数,
 -每次通话的平均时间。

2-图形分析
我们使用gprof --graph a.out命令来获取每个函数的以下数据,其中包括
 -在每个部分中,一个功能都标有索引号。
 -在函数上方,有一系列调用该函数的函数。
 -在函数下方,有一个函数调用的函数列表。

要获取更多信息,请查看https://sourceware.org/binutils/docs-2.32/gprof/

答案 19 :(得分:0)

我在 LINUX 上使用性能工具的经验

1.缓存研磨

太慢了!

2. gprof

导致被分析的进程在 fork() 系统调用中挂起。

3.谷歌 CPU 分析器

不稳定以至于无法使用,因为它使用信号并从信号处理程序调用某个 libunwind。工作时异常可用的输出。还会导致被分析的进程在 fork() 系统调用中挂起。

4.英特尔 vtune 2019

后处理器经常挂起。没有可用的来源归属。工具无法使用。

5.原生 linux 性能

我发现它无法使用,因为我无法操作后处理工具。例如。不可能按 CPU 时间对函数进行排序并包括/排除调用的函数 (perf-report)。我发现无法看到归因于 CPU 时间的源代码行 (perf-annotate)。

6.结论

您的里程可能会有所不同。特别是如果您正在分析仅运行几秒钟的小型可执行文件。拥有 root 权限和安装/卸载工具的权限可能也很重要。能够使用所用操作系统附带的工具可能很重要!如果您必须使用某个非本地工具集(编译器、链接器和已编译的库),您可能会走运。