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

新手上路,请多包涵

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

原文由 Gabriel Isenberg 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 629
2 个回答

如果您的目标是使用探查器,请使用建议的探查器之一。

但是,如果您赶时间并且可以在调试器下手动中断您的程序,而它主观上很慢,那么有一种简单的方法可以找到性能问题。

只需将其暂停几次,每次都查看调用堆栈。如果有一些代码浪费了一定百分比的时间,20% 或 50% 或其他任何时间,这就是您在每个样本的行为中捕获它的概率。因此,这大致是您将看到它的样本的百分比。不需要有根据的猜测。如果您确实猜测问题是什么,这将证明或反驳它。

您可能会遇到多个大小不同的性能问题。如果您清除其中任何一个,其余的将占更大的百分比,并且在随后的传球中更容易发现。这种 _放大效应_,当复合多个问题时,可以导致真正巨大的加速因素。

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

  1. 他们没有在指令级别进行总结,并且
  2. 在存在递归的情况下,它们给出了令人困惑的摘要。

他们还会说它只适用于玩具程序,而实际上它适用于任何程序,而且它似乎在更大的程序上效果更好,因为它们往往有更多的问题要找到。他们会说它有时会发现不是问题的东西,但只有当你看到某样东西 才会这样说。如果您在多个样本上发现问题,那就是真实的。

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

PPS 作为一个粗略的概括,您的软件中的抽象层越多,您就越有可能发现这是性能问题的原因(以及获得加速的机会)。

补充:这可能并不明显,但堆栈采样技术在存在递归的情况下同样有效。原因是删除一条指令所节省的时间近似为包含它的样本的分数,而不管它在样本中可能出现的次数。

我经常听到的另一个反对意见是:“ _它会随机停在某个地方,它会错过真正的问题_”。这来自对真正问题的先验概念。性能问题的一个关键特性是它们违背预期。抽样告诉你有问题,你的第一反应是不相信。这是自然的,但您可以确定它是否发现问题是真实的,反之亦然。

补充:让我对它的工作原理做一个贝叶斯解释。假设有一些指令 I (调用或其他)在调用堆栈上的某个部分 f 时间(因此花费那么多)。为简单起见,假设我们不知道 f 是什么,但假设它是 0.1, 0.2, 0.3, … 0.9, 1.0,并且每种可能性的先验概率都是 0.1,所以所有这些成本都同样可能是先验的。

然后假设我们只取 2 个堆栈样本,我们在两个样本上都看到指令 I ,指定观察 o=2/2 。这给了我们新的估计频率 fI ,根据这个:

 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

最后一列表示,例如, f >= 0.5 的概率为 92%,高于之前假设的 60%。

假设先前的假设不同。假设我们假设 P(f=0.1) 是 0.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

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

另一种看待它的方式称为 继承规则。如果你掷硬币两次,两次都出现正面,这说明硬币的可能重量是什么?受人尊敬的回答方式是说它是 Beta 分布,平均值为 (number of hits + 1) / (number of tries + 2) = (2+1)/(2+2) = 75%

(关键是我们不止一次看到 I 。如果我们只看到一次,除了 f > 0 之外,这并不能告诉我们太多。)

因此,即使是非常少量的样本也可以告诉我们很多关于它所看到的指令成本的信息。 (平均而言,它会以与其成本成正比的频率看到它们。如果采取 n 样本,并且 f 是成本,那么 I 将出现在 nf+/-sqrt(nf(1-f)) 样品上。例如, n=10f=0.3 ,即 3+/-1.4 )样品。


补充:为了直观地了解测量和随机堆栈采样之间的区别:

现在有分析器对堆栈进行采样,即使在挂钟时间,但 结果 是测量值(或热点路径或热点,“瓶颈”可以轻松隐藏)。他们没有向您展示(他们很容易)是实际样品本身。如果您的目标是 找到 瓶颈,那么您需要查看的瓶颈数量 平均 为 2 除以所需时间的分数。因此,如果花费 30% 的时间,则平均 2/.3 = 6.7 个样本会显示它,而 20 个样本显示它的机会是 99.2%。

这是检查测量值和检查堆栈样本之间差异的现成说明。瓶颈可能是这样的一个大块,也可能是许多小块,没有区别。

在此处输入图像描述

测量是水平的;它告诉您特定例程需要多少时间。采样是垂直的。如果有任何方法可以避免此时整个程序正在做什么, _并且如果您在第二个示例上看到它_,那么您已经找到了瓶颈。这就是不同之处——看到花费时间的全部原因,而不仅仅是多少。

原文由 Mike Dunlavey 发布,翻译遵循 CC BY-SA 4.0 许可协议

使用调试软件 如何识别代码运行缓慢的地方?

只是认为你在运动时有障碍物,那么它会降低你的速度

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

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

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

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

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

我认同

原文由 user10306682 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题