如何找到在Linux上运行的C++应用程序中运行缓慢的代码区域?


当前回答

使用具有以下选项的Valgrind:

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

这将生成一个名为callgrind.out.x的文件。使用kcachegrind工具读取该文件。它会给你一个图形化的分析结果,比如哪一行花费多少。

其他回答

事实上,没有多少人提到google/基准测试,这有点让人惊讶,虽然固定代码的特定区域有点麻烦,特别是如果代码库有点大的话,但是我发现这在与callgrind结合使用时非常有用

IMHO识别导致瓶颈的工件是这里的关键。不过,我会先尝试回答以下问题,然后根据这些问题选择工具

我的算法正确吗?有锁被证明是瓶颈吗?是否有一段特定的代码被证明是罪魁祸首?IO如何处理和优化?

valgrind与callgrind和kcachegrind的结合应该能对以上几点提供一个不错的估计,一旦确定某段代码存在问题,我建议做一个微基准测试——谷歌基准测试是一个很好的开始。

如果没有一些选项,运行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()”是最耗时的任务。“Self”显示每个函数本身花费的时间,而不是与依赖项一起花费的时间。

在工作中,我们有一个非常好的工具,它可以帮助我们监控我们想要的日程安排。这已多次有用。

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

您检索包含所有数据的所谓大缓冲区,一个小接口解析它并显示带有名称(up/down+value)的事件,就像示波器使用颜色(在.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函数使用几条装配线尽快检索时钟时间戳,然后在缓冲区中设置一个条目。我们还有一个原子增量来安全地找到存储日志事件的索引。当然,缓冲区是圆形的。

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

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

(维基百科)Valgrind本质上是虚拟的机器使用准时制(JIT)编译技术,包括动态重新编译。没有来自的内容原始程序始终运行直接在主机处理器上。相反,Valgrind首先翻译将程序转换为临时的、更简单的形式称为中间表示(IR)是处理器中性的,转换后,工具(见下文)可以自由使用无论它想要什么样的转变在Valgrind翻译之前IR返回到机器代码中主机处理器运行它。

Callgrind是一个基于此的剖析器。主要的好处是,您不必运行应用程序数小时就能获得可靠的结果。因为Callgrind是一个非探测型剖面仪,所以即使一秒钟的运行也足以获得可靠的结果。

另一个基于Valgrind的工具是Massif。我使用它来分析堆内存使用情况。它工作得很好。它的作用是为您提供内存使用情况的快照--详细信息What hold What percentage of memory,and WHO has put it there。这些信息在应用程序运行的不同时间点可用。

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

对于CPU绑定的应用程序:

在DEBUG模式下使用探查器来识别代码中有问题的部分然后切换到RELEASE模式,注释掉代码中有问题的部分(不加任何内容),直到看到性能的变化。

对于I/O绑定应用程序:

在RELEASE模式下使用探查器来识别代码中有问题的部分。


N.B.

如果你没有剖析器,就用穷人的剖析器。调试应用程序时单击暂停。大多数开发人员套件将使用注释的行号分解成程序集。从统计上看,你很可能会在一个消耗了大部分CPU周期的区域着陆。

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

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