超越堆栈采样:C++性能分析工具

154

一个骇客的故事

日期是12/02/10。离圣诞节还有几天,我作为一名 windows 程序员已经遭遇了严重的障碍。我尝试使用 AQTime、Sleepy、Shiny 和 Very Sleepy,而 VTune 正在安装中。我尝试使用 VS2008 分析器,但它往往无法理解并且非常困难。我使用了随机暂停技术,检查了调用树和函数跟踪。但令人难受和痛苦的事实是,我正在处理的应用程序包含超过一百万行代码,可能还有另外一百万行的第三方应用程序。

我需要更好的工具。我阅读了其他话题。 我尝试了每个话题中列出的每个分析器。必须有比这些低劣而昂贵的选项或为几乎没有收益而做出的荒谬数量的工作更好的东西。更进一步使问题复杂化的是,我们的代码有很多线程,并运行许多 Qt 事件循环,其中一些由于时间延迟而在重载时崩溃。不要问我为什么我们要运行多个事件循环,没有人可以告诉我。

在 Windows 环境中有类似 Valgrind 的选项吗?
有比我已经尝试过的一大堆破碎工具更好的东西吗?
有没有旨在与 Qt 集成并具有事件队列有用显示的工具?

以下是我尝试的所有工具列表,其中真正有用的工具用斜体表示:

  • AQTime: 相当不错!在深度递归时会有些麻烦,但对于这些情况,调用图是正确的,并且可以用来澄清任何可能存在的困惑。虽然不是完美的工具,但值得一试。它可能适合您的需求,并且大部分时间对我来说足够好。
  • 调试模式下的随机暂停攻击: 很多时候信息不足。一个好的工具,但不是完整的解决方案。
  • Parallel Studios: 核心选项。比较突兀、奇怪,但功能强大。 我认为您应该使用30天的评估版,并确定它是否适合您的需求。 它确实很酷。
  • AMD Codeanalyst: 非常棒,易于使用,但非常容易崩溃,但我认为这是环境问题。 我建议尝试一下,因为它是免费的。
  • Luke Stackwalker: 在小型项目上运行良好,我们的项目运行良好有点费劲。 一些好的结果,对于我的个人任务,它绝对可以取代Sleepy。
  • PurifyPlus: 不支持Win-x64环境,尤其是Windows 7。否则非常出色。 我在其他部门的一些同事中有很多人都很喜欢它。
  • VS2008 Profiler: 在函数跟踪模式下以所需的分辨率产生超过100GB的输出。好的方面是可以产生可靠的结果。
  • GProf: 需要GCC才能达到中等效果。
  • VTune: VTune的W7支持边缘犯罪。否则非常出色。
  • PIN: 我需要自己编写工具,因此这是一种最后的选择。
  • Sleepy\VerySleepy: 对于较小的应用程序有用,但在这里无法帮助我。
  • EasyProfiler: 如果您不介意手动注入代码以指示在哪里进行插装,则还不错。
  • Valgrind:仅限*nix环境,但在该环境下非常出色。
  • OProfile:仅限Linux。
  • Proffy:他们枪杀了野马。

建议的工具我没有尝试过:

  • XPerf:
  • Glowcode:
  • Devpartner:

注:目前使用Intel环境、VS2008、boost库以及Qt 4+,还有一个让人头疼的问题:通过Trolltech实现的Qt/MFC集成。


现在:将近两周过去了,看起来我的问题已经解决了。感谢各种工具(包括列表中的几乎所有工具和我个人的一些技巧),我们找到了主要的瓶颈。然而,我仍将继续测试、探索和尝试新的分析器以及新的技术。为什么?因为我要向你们证明自己能行,而你们很棒。这可能会稍微延长时间表,但我仍然非常兴奋地尝试新工具。

简介
除了许多其他问题外,最近一些组件被切换到了错误的线程模型,导致由于底层代码突然不再是多线程的,出现了严重的停顿问题。我不能透露更多信息,因为这违反了我的保密协议,但我可以告诉你,即使是普通的检查或正常的代码审核也永远不会发现这个问题。没有分析器、调用图和随机暂停结合起来使用,我们仍然会对美丽的蓝色天空弥漫着怒火。幸运的是,我与一些我曾经遇到过的最好的黑客共事,并且我可以访问一个充满了优秀工具和优秀人才的“宇宙世界”。

各位,我非常感激你们,遗憾的是我没有足够的声誉去奖励每个人。我仍然认为这是一个比SO上现有答案更好的问题。因此,在接下来的三个星期里,我将提供我能够提供的最大赏金,并授予一个我认为不常见的最好的工具的答案。三周后,我们希望积累一个分析器的最终概况。

精华版
使用性能分析工具。即使是 Ritchie、Kernighan、Bentley 和 Knuth 也在使用它们。不管你认为自己有多强,都请使用性能分析工具。如果你目前的工具不能达到预期效果,就寻找其他工具。如果找不到合适的工具,就自己开发一个。如果自己无法开发或者只遇到小的问题,或者卡住了,可以使用随机暂停技术。如果所有的方法都失败了,可以雇用一些研究生来开发性能分析工具。


长远视角版
因此,我想写一篇回顾文章。我选择与 Parallel Studios 大量合作,部分原因是它实际上是建立在 PIN 工具之上的。由于曾经与一些涉及研究人员进行过学术交往,所以我认为这可能是一种质量标志。幸运的是,我是正确的。虽然 GUI 有点糟糕,但我发现 IPS 非常有用,尽管我不能轻易地为每个人推荐它。关键是,在许多其他性能分析工具(如 AQT)中,没有明显的方法来获取代码行级别的命中次数,这是我用于检查分支选择率等问题非常有用的东西。总体而言,我也喜欢使用 AQTime,并且我发现他们的支持非常响应。同样需要澄清的是:他们的许多功能都不起作用,其中一些在 Win7x64 上甚至会导致崩溃。XPerf 的性能表现也很出色,但对于需要进行详细采样的某些应用程序来说,速度非常慢。

目前,我必须说,我认为在 W7x64 环境中,没有一个明确的选项可以对 C++ 代码进行性能分析,但肯定存在一些根本不提供任何有用服务的选项。


18
你考虑过换一份工作吗? :) - Nikolai Fetissov
10
除了这里,我还能在哪里解决这么难的谜题?我想我可以回去搞内核开发,但那的报酬不如这个。 - Jake Kurzer
3
@Kos 我认为,要让gprof有用,你必须使用gcc工具集并编译-pg,否则它不会生成gprof.out文件。在OP的情况下,他似乎正在使用msvc,这排除了使用gprof的可能性。再说了,如果列表中的其他工具不能满足他的需求,我也不认为gprof会更好。 - greatwolf
2
@Marc Gravell,我想这很公平。对我来说,这似乎是一个奇怪的启发式方法,即最好维护的帖子突然进入社区领域,实际上产生了这样一种情况:你更新和维护问题或答案的次数越多,你在整个社区中得到的回报就越少。我应该去元吗? - Jake Kurzer
2
有没有人想要一次回顾,考虑到我现在对分析器的了解? - Jake Kurzer
显示剩余10条评论
19个回答

68

首先:

时间采样分析器比CPU采样分析器更健壮。我对Windows开发工具不是非常熟悉,所以无法判断哪个是哪个。大多数分析器都是CPU采样的。

CPU采样分析器每N条指令抓取一次堆栈跟踪。
此技术将显示出CPU限制的代码部分。如果这是应用程序的瓶颈,那就很棒。但如果您的应用程序线程大部分时间在争夺mutex,则不太理想。

时间采样分析器每N微秒抓取一次堆栈跟踪。
此技术将聚焦于“缓慢”的代码,无论是CPU限制、阻塞IO限制、mutex限制还是缓存风暴影响的代码段。简而言之,无论哪个代码片段减缓了应用程序的速度,都会显现出来。

因此,尽可能使用时间采样分析器,特别是在对线程化代码进行分析时。

其次:

采样分析器生成海量数据。这些数据非常有用,但通常太多,不容易使用。这时候,一个性能数据可视化工具将会帮助很多。我发现用于性能数据可视化的最佳工具是gprof2dot。不要被名称所迷惑,它可以处理各种采样分析器输出(AQtime、Sleepy、XPerf等)。一旦可视化指出有问题的函数,就可以返回原始性能数据以获得更好的提示。

gprof2dot工具生成一个dot图形描述,然后将其输入到graphviz工具中。输出基本上是一个调用图,函数通过对应的颜色来反映对应函数对应对应对应应用程序的影响。 alt text

以下是一些获取漂亮的gprof2dot输出的技巧。

  • 我在图表中使用--skew 0.001,这样就可以轻松地看到热点代码路径。否则,int main()会主导整个图表。
  • 如果你在使用C++模板时进行任何疯狂的操作,你可能需要添加--strip。特别是在使用Boost时更需要这样做。
  • 我使用OProfile生成采样数据。为了获得良好的输出结果,我需要将其配置为加载来自第三方和系统库的调试符号。一定要做相同的步骤,否则你会发现CRT占用了应用程序20% 的时间,而实际上发生的事情是malloc破坏了堆并占用了15%的时间。

虽然我不确定这是否是解决我的问题的完整答案,但gprof2dot已经进入了我的庞大工具库,并迅速成为了一个喜爱的工具。我认为这值得奖励! - Jake Kurzer
2
我曾经问过这个问题 Linux 时间样本分析器。OProfile 最终应该会得到基于时间的采样。他们产生非常高质量的输出,所以一旦他们添加了这个功能,我就会使用它们。除此之外,我有一个朋友为分析编写了一个 gdb + backtrace 的解决方案。非常 hacky,但它确实找到了瓶颈。 - deft_code
@deft_code:“hack together a gdb + backtrace solution for profiling. Very hacky, but it did find the bottleneck.” 你证实了我的不断抱怨 :) 有些人希望性能分析看起来漂亮,但如果结果是你需要的,就用管用的方法,而不是漂亮的方法。 - Mike Dunlavey
我同意Mike Dunlavey的观点。像XPerf/WPA这样的工具看起来非常漂亮和强大,但是要想弄清楚如何使用这些工具需要一段时间,而且说到底,随机暂停是如此简单并提供更好的信息来解决问题。更多自动化的解决方案似乎往往会过滤掉解决瓶颈所需的关键信息。 - JDiMatteo
Visual Studio自带一个时间采样分析器。https://learn.microsoft.com/en-us/visualstudio/profiling/understanding-sampling-data-values?view=vs-2017 - Ben

17

当您尝试随机暂停时发生了什么?我在一个庞大的应用程序中经常使用它。您说它没有提供足够的信息,建议您需要更高的分辨率。有时人们需要一点帮助来理解如何使用它。

在 VS 下,我进行配置以使堆栈显示不显示函数参数,因为这会使堆栈显示完全无法阅读。

然后我在等待时间内按“暂停”键约10次。我使用 ^A、^C 和 ^V 将它们复制到记事本中以供参考。然后我研究每个样本,试图弄清楚它当时正在尝试完成的事情。

如果它在2个或多个样本中都尝试完成某些任务,并且这些任务不是必需的,则我找到了一个实际问题,并且大致知道修复它将节省多少。

有些事情您真的不需要知道,例如精确的百分比不重要,第三方代码内部发生的事情也不重要,因为您无法对其进行任何操作。您可以对其中每个堆栈样本中显示的可修改的代码集合中的调用点进行调整。

以下是我发现的一些问题的示例:

  • 在启动期间,它可能会深入30层,试图从 DLL 资源中提取国际化的字符字符串。如果实际字符串被检查,很容易发现这些字符串实际上并不需要进行国际化,例如用户从未看到的字符串。

  • 在正常使用过程中,一些代码无意中设置了某个对象中的“已修改”属性。该对象来自于一个捕获更改并触发遍及整个数据结构的通知的超类,以一种难以预见的方式操作 UI、创建和销毁对象。这种情况可能经常发生——通知的意外后果。

  • 从行到列填充表格,逐个单元格填写。事实证明,如果一次性使用值数组构建行,则速度更快。

  • P.S. 如果应用多线程,在暂停时所有线程都会暂停。查看每个线程的调用栈。很有可能只有其中一个是真正的罪魁祸首,而其他的正在空闲。


    2
    评论?评论?这是斯巴达!我...抱歉,不知道那是从哪里来的。不,这段代码让克林贡歌剧看起来可读,而且它的文档也几乎没有。实际上,我认为它的文档要少得多...哦天啊。 - Jake Kurzer
    3
    QTMFC 集成?哦,太棒了,你已经遇到了 复杂恶劣,而你甚至还没有涉及应用程序特定的代码。 - Ben Voigt
    5
    QT/MFC?那不会产生三个头来回晃动并称每个想法都是最愚蠢的想法的突变孩子吗?呃......我跑题了......如果您正在使用任何MFC Socket类,请立即重写您的socket代码,然后进行性能评估。在CSocket代码中有很多地方使用消息循环版本的WaitForSingleObject函数,我发现这会降低性能。我拼命想不起等待函数的名称... :/ - JimR
    2
    哦天啊,相信我,它正如你所想的那样混乱。 - Jake Kurzer
    3
    @Jake: 并不会给你带来太多安慰,但这就是图灵完备性的辉煌之处。任何语言,无论高级还是低级,都有其无限使用不当的能力。 - Mike Dunlavey
    显示剩余21条评论

    8

    目前是Intel环境。不过我会记住的! :) - Jake Kurzer
    4
    @Jake:我不确定你的意思。AMD CodeAnalyst 不需要 AMD 芯片,它可以在大多数 x86 或 x64 (即 x86-64/IA-64/AMD64) 芯片上工作,包括英特尔芯片。 - Adam Rosenfield
    1
    显然,我是文盲!这是个好消息。我明天会试一下并更新问题。 - Jake Kurzer
    到目前为止,以我需要的分辨率进行采样时非常不稳定。 - Jake Kurzer
    @Adam: 最近我在一台英特尔Pentium IV机器上尝试了代码分析器,它只提供基于时间的采样,没有任何线程使用或相关信息...所得到的信息量非常普通..此外,它还导致Visual Studio的Qt集成崩溃..我感到非常不满意 :( - smerlin
    每次都崩溃了,即使在更宽的采样分辨率(如10毫秒)下也是如此。 - Jake Kurzer

    8

    您是否有一个MFC OnIdle函数? 在过去,我曾经修复过一个几乎实时的应用程序,在设置为19.2K速度时会丢失串行数据包,这是PentiumD本应能够跟上的。 OnIdle函数是导致问题的原因。 我不确定QT是否具有该概念,但我也会检查一下。


    2
    我们实际上有一个OnIdle,并且由于我们的QTMFC集成,它正在通过QT事件循环流动。哦天啊。 - Jake Kurzer
    结果证明这直接导致了我们的解决方案,所以虽然这不是对问题的完美答案,但我认为这个问题是无法回答的。 - Jake Kurzer

    4
    关于VS Profiler的问题--如果它生成了如此大的文件,也许你的采样间隔太频繁了?尝试降低它,因为你可能已经有足够的样本了。
    最好的方法是确保在实际运行问题区域之前不要收集样本。因此,在暂停收集的情况下开始收集,让程序执行其“缓慢活动”,然后开始收集。你只需要最多20秒的收集时间。在此之后停止收集。
    这应该有助于减少样本文件的大小,并且仅捕获对于你的分析必要的内容。

    明天我会试一试。 - Jake Kurzer

    4
    我已经成功地在 Windows 上使用了PurifyPlus。虽然它不便宜,但 IBM 提供了一个略有缺陷的试用版本。对于使用quantify进行分析,您只需要pdb文件并链接 /FIXED:NO。唯一的缺点是:不支持Win7/64。

    很遗憾,我们的主要目标是Win7。我会在主帖中加入这些信息。 - Jake Kurzer
    1
    PurifyPlus的当前版本支持Win7/64。 - hmuelner

    3

    Easyprofiler - 我在这里还没有看到它的提及,所以不确定你是否已经看过它。它采用了一种稍微不同的方法来收集度量数据。使用其编译时剖析方法的缺点是必须对代码进行更改。因此,您需要大致知道缓慢可能在哪里,并在那里插入剖析代码。

    然而,根据您最新的评论,听起来您至少有了一些进展。也许这个工具可以为您提供一些有用的指标。如果没有其他好处,它至少有一些非常漂亮的图表和图片:P


    3

    查看 XPerf

    XPerf 是微软提供的免费、非侵入式和可扩展的性能分析工具。它是由Microsoft开发用于评估Windows操作系统性能。


    3

    再提供两个工具建议。

    Luke Stackwalker有一个可爱的名字(即使它对我来说有点太努力了),它不会花费你任何费用,并且你可以获得源代码。 它声称还支持多线程程序,所以绝对值得一试。

    http://lukestackwalker.sourceforge.net/

    此外,Glowcode也很值得使用:

    http://www.glowcode.com/

    不幸的是,我已经有一段时间没有做PC工作了,所以我还没有尝试过这两个工具。 我希望这些建议能帮到你。


    3
    如果你对事件循环持怀疑态度,可以重写QCoreApplication::notify()并进行一些手动分析(一个或两个发送者/事件到计数/时间的映射)吗?
    我想,你首先要记录事件类型的频率,然后仔细检查这些事件(哪个对象发送它,它包含什么等)。跨线程的信号会被隐式排队,因此它们最终会进入事件循环(显式排队连接也是如此,显然)。
    我们已经这样做了,以便在我们的事件处理程序中捕获和报告异常,因此,每个事件都会通过那里。
    只是一个想法。

    这是一个很棒的想法!我不习惯QT环境,之前大部分工作都是用pyGTK完成的。谢谢! - Jake Kurzer
    你有推荐的方法来获取和解决给定信号的性质吗? - Jake Kurzer
    我只对使用QStateMachine :: SignalEvent的信号进行了处理,但似乎并不相同。源应该仍然是“QObject * object”参数。也许MetaCall是所有信号的类型(看起来很可能),但我不确定。这有点超出了我的经验范围,但是研究Qt源代码可能会得到一些真相。(或者,在SO上提出一个更具针对性的关于排队信号调用的问题.. :)) - Macke

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接