【问题标题】:How can I get better profiling?如何获得更好的分析?
【发布时间】:2009-03-07 15:48:33
【问题描述】:

我需要对程序进行概要分析,以查看是否需要对性能进行任何更改。我怀疑有必要,但首先测量是要走的路。这不是那个程序,但它说明了我遇到的问题:

#include <stdio.h>
int main (int argc, char** argv)
{
  FILE* fp = fopen ("trivial.c", "r");
  if (fp)
  {
    char line[80];
    while (fgets (line, 80, fp))
      printf (line);

    fclose (fp);
  }
  return 0;
}

这是我用它做的:

% gcc trivial.c -pg -o trivial
% ./trivial
...
% gprof trivial gmon.out

诚然,这是一个微不足道的程序,但我原以为它会在分析雷达上产生某种影响。它没有:

                                  called/total       parents 
index  %time    self descendents  called+self    name       index
                                  called/total       children

                0.00        0.00       1/1           __start [1704]
[105]    0.0    0.00        0.00       1         _main [105]

-----------------------------------------------

  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
  0.0       0.00     0.00        1     0.00     0.00  _main [105]

Index by function name

 [105] _main              

有人可以在这里指导我吗?我希望输出反映它至少调用了 fgets 和 printf 14 次,而且它确实击中了磁盘 - 当然应该有一些测量时间。

当我在真实程序上运行相同的命令时,我会列出更多函数,但即便如此,它也不是一个完整的列表 - 只是一个示例。

也许 gprof 不是正确使用的工具。是什么?

这是在 OS X Leopard 上。

编辑:我运行了真正的程序并得到了这个:

% time real_program
real      4m24.107s
user      2m34.630s
sys       0m38.716s

【问题讨论】:

    标签: c++ c macos gcc profiling


    【解决方案1】:

    我认为您可以尝试各种 Valgrind 工具,尤其是 callgrind(用于获取程序中发生的每个调用的调用计数和包含成本)。

    valgrind 输出有各种不错的可视化工具。不过我不知道 OS X 的特定工具。

    【讨论】:

    • 太好了,谢谢。我有 VMWare,还有一大堆我可以在其下运行 valgrind 的 linux vm。我使用哪个操作系统都没关系。
    • 最近我一直在使用 KCachegrind 来可视化 valgrind 分析输出。
    【解决方案2】:

    默认情况下,gprof 向您显示有限的数据。哪个好。查看您的输出 - 它仅提及 main (这是默认设置)。现在,查看calls 列——这就是你想要的。但是对于其他功能,请尝试:

    gprof -e main -f printf -f fgets trivial > gprof.output
    

    这是一些命令的link。另外,在您的系统上尝试man gprofHere 解释数据的方法。

    另外,查找ltracestraceptrace(如果可用——我不再记得它们是否都在 OSX 上)——它们很有趣!

    【讨论】:

      【解决方案3】:

      Shark 是开发人员工具中包含的分析器。

      【讨论】:

      • 是的,但是当我按下“停止”按钮时鲨鱼内核恐慌。我正在寻找一些基于命令行的东西。
      【解决方案4】:

      在分析您的代码之前,您需要查看您的程序在哪里花费时间。在 time(1) 下运行它可以查看对应的用户、系统和挂钟时间。仅当用户时间接近挂钟时间时,分析您的代码才有意义。如果用户和系统时间与挂钟时间相比非常小,那么您的程序是 I/O 绑定的;如果系统时间接近挂钟时间,则您的程序受内核限制。在这两种情况下,在 strace -c 或合适的 dtrace 脚本下运行程序以确定每个系统调用所花费的时间。

      【讨论】:

      • 我在上面的问题中输入了数字。
      • 太棒了!您可以通过查找热点并优化它们来提高 2.34 秒的用户时间。您可以通过最小化操作系统调用和您要求操作系统为您执行的工作来提高 38 秒的系统时间。另外 72 秒是 I/O,您可以通过缓存或更好的数据处理将其最小化。
      • 听起来很简单 :) 感谢您的帮助。
      【解决方案5】:

      分析并不表示磁盘访问,只是调用了哪些函数,并且由于 VM 缓存,这些函数不具有代表性。

      Valgrind doesn't work well on OS X.

      使用 Leopard,您可以使用 Dtrace 实用程序;我没有使用过它,但它可能会为您提供您正在寻找的信息。

      【讨论】:

      • 感谢 Dtrace 提示 - 我会看看。
      【解决方案6】:

      缺少某些函数通常意味着这些函数未编译用于分析。具体来说,要分析使用诸如printf 之类的标准函数的代码(我猜几乎总是这样),您需要使用分析支持编译的 C 库版本。我不熟悉 OS X,但在 Linux 上我需要安装一个包含 libc_p 库的 libc6-prof 包。

      顺便说一句,我相信 OS X(或者可能是 XCode?)带有一个分析工具。它不如gprof 方法精确,因为它使用采样,但是您可以在任何程序上运行它而无需特殊编译。

      【讨论】:

        【解决方案7】:

        看一下您的程序,因为您正在使用文件处理(仅),它还取决于启用的任何缓存。因此,请注意,您的分析结果可能会因您的缓存行为而异。

        【讨论】:

          【解决方案8】:

          在这个行业有一些普遍接受的信念,我建议你仔细研究一下。

          一个是发现性能问题的最佳(如果不是唯一)方法是测量每个子例程所花费的时间并计算它被调用的次数。

          这是自上而下的。它源于一种信念,即森林比树木更重要。它基于关于“代码速度”和“瓶颈”的神话。这不是很科学。

          性能问题更像是一个错误,而不是量化问题。它做错了什么是浪费时间,它需要被修复。它基于一个简单的观察:

          缓慢是因为不合理的原因花费的时间。

          要找到它,请在时钟时间的随机片段中对程序状态进行采样,并调查其原因。

          如果某些原因导致速度变慢,那么仅这一事实就会将其暴露给您的样本。所以如果你拿了足够多的样本,你就会看到它。通过显示它的样本比例,您将大致了解它花费了多少时间。

          判断是否有充分理由花费了一小段时间的一个好方法是仔细查看调用堆栈。堆栈上的每个函数调用都有一个隐含的原因,如果其中任何一个原因很差,那么整个样本的原因就是差。

          一些分析器会在语句级别告诉您每条语句的成本。

          就个人而言,我只是随机停止程序几次。出现在多个样本上的任何调用都可能引起怀疑。它永远不会失败。

          您可能会说“这不准确”。它非常准确。它精确地指出了导致问题的指令。它不会为您提供 3 位小数的计时精度。 IE。它不适合测量,但非常适合诊断。

          你可能会说“递归呢?”。嗯,怎么样?

          您可能会说“我认为这仅适用于玩具程序。”那只是希望。事实上,大型程序往往有更多的性能问题,因为它们有更深的堆栈,因此有更多的机会进行不合理的调用,并且采样发现它们很好,谢谢。

          对不起,我是个脾气暴躁的人。我只是讨厌在应该以科学为基础的领域中看到神话。

          MORE

          【讨论】:

          • 我同意教授(和邻居)的观点,我已经在调试器中多次中断程序,并且一直看到它深埋在 std::vector::iterator 调用中。我只是在寻找需要解决哪些 组迭代调用。该技术无法帮助我选择 - 无法解决所有问题。
          • @Paul:太好了!答案就在你面前。从代码中可能出现在多个堆栈上的特定点调用迭代器。这些是您的成本点。
          • ... 如果你真的对这些无能为力(比如如果你真的不想自己索引它),也许你可以上一层或两层,然后摆脱出现在那里的东西。
          • ... 或者您可能会明白为什么迭代器很慢。也许他们不仅仅是索引,而是做一些复杂的事情。即使“信息隐藏”是你的周期,所以你有权知道。
          【解决方案9】:

          对于您上面给出的示例代码,如果您对调用堆栈进行多次采样,您将基本上看到这些堆栈,按一定比例:

          -------------------------------------
          ...
          main.c:  4 call _fopen
          ...        call _main
          -------------------------------------
          ...
          main.c:  8 call _fgets
          ...        call _main
          -------------------------------------
          ...
          main.c:  9 call _printf
          ...        call _main
          -------------------------------------
          ...
          main.c: 11 call _fclose
          ...        call _main
          

          比例会大致告诉您每次通话所花费的时间比例。您可能不会看到太多其他内容,因为与 I/O 库调用相比,“独占”时间基本上为零。这就是堆栈示例可以告诉您的信息 - 无论程序有多大,花费您最多的精确语句,以及大致多少。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-11-26
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多