带有循环检测的 Callgrind 性能分析
Posted
技术标签:
【中文标题】带有循环检测的 Callgrind 性能分析【英文标题】:Callgrind performance analysis with cycle detection 【发布时间】:2017-10-28 18:20:10 【问题描述】:我第一次尝试使用 Callgrind/Kcachegrind 来分析我的 C++ 应用程序,我注意到需要更多时间的两个函数是:
-
(50% 自我)和
do_lookup_x(15% 自己)
现在,根据我的理解,周期 1 与递归调用函数所花费时间的估计有关,但我不太清楚我应该如何解释在这里花费的如此长的时间。如果有一些周期,我想看看哪个函数被调用得更频繁,最后占用更多的 CPU 时间。如果我禁用循环检测(视图->循环检测),那么循环 1 会消失,但“自我”时间总计约为 60%,我不确定这是最好的做法。 关于 do_lookup_x 我完全一无所知...
你能解释一下我应该如何解释这些结果吗?
提前致谢。
【问题讨论】:
Self
时间应该计算正确。 callgrind 中的循环检测是启发式的,因为 callgrind/cachegrind 输出没有完整的调用堆栈,它只记录被调用者-调用者对。 perf
和 google-perftools
(pprof
) 都更适合函数调用堆栈捕获(当且仅当您的项目启用了 -fno-omit-frame-pointer
选项)并且没有像 Kcachegrind 这样漂亮的 GUI。 perf record -g
输出可以用github.com/jrfonseca/gprof2dot 作为图片查看。另外:如果您有 >10% 的 do_lookup_x
- 您的程序太短而无法分析;试试LD_BIND_NOW=1 ./prg
@osgx 谢谢,但我真正的问题是:我可以安全地忽略第 1 周期占用的 50% 并只分析其他函数吗?还是说有什么奇怪的事情正在发生?
Alessandro,哪个时间被周期“占用”了 50%? “包括。”时间可能不正确,自我时间应该是正确的(并且仅针对实际功能设置)。检查列在最前面的表,使用按自时间排序。 (您也可以张贴屏幕截图,并显示您的循环图表)
@osgx,50% 是“self”,而 96% 是“incl”。完整地说,我正在运行 OMNeT++ 模拟
在 Kcachegrind 中关闭循环检测并再次检查“self”次数。
【参考方案1】:
在 KCachegrind 中可能会错误地检测到循环: http://valgrind.org/docs/manual/cl-manual.html#cl-manual.cycles
6.2.4。避免循环 通俗地说,循环是一组以递归方式相互调用的函数。 ...
循环本身并不坏,但往往会使代码的性能分析变得更加困难。这是因为周期内调用的包容性成本是没有意义的。包含成本的定义,即函数的自身成本加上其被调用者的包含成本,需要函数之间的拓扑顺序。对于循环,这并不成立:循环中函数的被调用者包括函数本身。因此,KCachegrind 进行循环检测并跳过循环内调用的任何包含成本的可视化。此外,一个循环中的所有函数都被折叠成称为 Cycle 1 的人工函数。
现在,当一个程序暴露出非常大的循环时(对于某些 GUI 代码,或者在使用基于事件或回调的编程风格的一般代码中确实如此),您将失去很好的属性,让您可以通过以下调用链来查明瓶颈主要,以包容性成本为指导。此外,KCachegrind 失去了显示调用图中有趣部分的能力,因为它使用包容性成本来切断不感兴趣的区域。
尽管周期中的包容性成本毫无意义,但可视化的一大缺点激发了在 KCachegrind 中暂时关闭周期检测的可能性,这可能会导致误导性的可视化。但是,由于独立调用链的不幸叠加,通常会出现循环,以使配置文件结果会看到一个循环。以非常小的包容性成本忽略无趣的电话会打破这些循环。在这种情况下,通过不检测循环来错误处理循环仍然可以提供有意义的分析可视化。
尝试在 KCachegrind 的 View 菜单中关闭 Cycle Detection 并检查“Self”时间列,因为“Incl”会不正确。
您还可以尝试使用其他具有精确和完整功能堆栈保存的分析器。 https://github.com/jrfonseca/gprof2dot 脚本支持的许多分析器保存完整的堆栈,不仅是 callgrind/cachegrind 格式的被调用者-调用者对。
【讨论】:
【参考方案2】:我同意@osgx 的观点,即您需要一个不同的分析器,一个捕获整个调用堆栈的分析器。
那么,函数的包含时间百分比是一个非常简单的数字。 它只是该函数出现在其中的堆栈样本的一部分,无论它在单个样本中出现多少次。
这是一种思考方式。 - 假设每 10 毫秒采样一次,总共 100 秒,即 10,000 个样本。 - 假设函数 Foo 出现在 30% 的样本中,一次或多次。 - 这意味着如果您可以更改 Foo 使其几乎不需要时间,例如通过将其传递给一个非常快的子处理器,那么没有样本会看到它,因为它永远不会在堆栈上足够长的时间让样本打它。 - 所以这 30% 的样本会消失,程序将需要 70 秒而不是 100 秒。 - 这意味着 Foo 个人负责 30% 的时间(不考虑递归)。
其实我更喜欢this method,因为我更感兴趣的是找出问题所在,而不是需要 29% 还是 31%。 它需要任何东西,并且不会受到测量精度的影响。
【讨论】:
以上是关于带有循环检测的 Callgrind 性能分析的主要内容,如果未能解决你的问题,请参考以下文章
利用callgrind+gprof2dot+dot进行性能分析
利用callgrind+gprof2dot+dot进行性能分析