gprof的替代方案

174

除了gprof之外,还有哪些程序可以实现相同的功能?


2
你对哪些平台感兴趣? - osgx
2
我对Linux有兴趣。 - neuromancer
2
可能是 https://dev59.com/n3RC5IYBdhLWcg3wOOL1 的重复问题。 - Dour High Arch
13
我倾向于同意Gregory的观点,也许他应该贡献自己的答案。229比6,而这6个答案都是针对他自己的问题...... - Jean-Bernard Pellerin
5
这个问题怎么才能不被认为是不构建的问题? - JohnTortugo
@John 这将是新关闭原因下的过于宽泛——有很多很多答案。这是在一段时间前提出的,当时这些问题是可以接受的。尽管我不认为它应该有这么多的删除投票——它现在有8个 :( - tckmn
7个回答

205
(阅读文件)仅因历史原因而存在。如果您认为它能帮助您找到性能问题,那么它从未被宣传为这样的工具。 以下是该论文的说明:

该分析结果可用于比较和评估不同实现的成本。

该论文并没有说它可以用来识别要评估的不同实现,尽管它确实暗示在特定情况下可以这样做:

特别是当发现程序的小部分占据了其执行时间时。

那么对于不那么局部化的问题呢? 那些问题是否无关紧要? 不要对寄予从未宣称的期望。 它仅仅是一个测量工具,并且只能用于CPU绑定操作。

请使用这个代替。
这里有一个44倍加速的例子。
这里有一个730倍加速的例子。
这是一个8分钟的演示视频。
这里是统计学解释。
这里是对批评的回答。

关于程序有一个简单的观察。在给定的执行中,每条指令都为整体时间贡献了一部分(特别是call指令),即如果没有它,时间不会被花费。在此期间,该指令位于堆栈上**。当理解了这一点,您就可以看到-

体现了关于性能的某些错误观念,例如:

  1. 程序计数器抽样是有用的。
    只有在你有不必要的热点瓶颈,比如对标量值的大数组进行冒泡排序时,它才有用。一旦你将其转换为使用字符串比较的排序,它仍然是瓶颈,但程序计数器抽样将看不到它,因为现在热点在字符串比较中。另一方面,如果它要对 扩展 程序计数器(调用栈) 进行采样,则显示了字符串比较被调用的点,排序循环。事实上,gprof 就是试图解决仅限于程序计数器抽样的局限性的一种尝试。

  2. 计时函数比捕获耗时的代码行更重要。
    这种迷思的原因是 gprof 不能捕获堆栈样本,所以它会对函数进行计时、计算它们的调用次数并尝试捕获调用图。然而,一旦确定了一个代价高昂的函数,仍需要查找其中负责时间的那些行。如果有堆栈样本,您就不需要查看,这些行将出现在样本中。(典型的函数可能有 100-1000 条指令。函数 调用 是 1 条指令,因此定位昂贵调用的工作精度更高 2-3 个数量级。)

  3. 调用图很重要。
    你需要知道程序的是不是花费太多时间在某个函数里,而是为什么它花费时间在那个函数里。当它在一个函数中花费时间时,堆栈上的每一行代码都给出了其中一条链的推论,解释它为什么会在那里。如果您只能看到一部分堆栈,那么您只能看到一部分原因,因此您无法确定该时间是否实际上是必要的。 调用图告诉您什么?每个弧线告诉您,某个函数 A 在某个时间的进程中调用了某个函数 B 的一些时间比例。即使 A 只有一条这样的代码行调用 B,那条行也只提供了很小的部分理由。如果您很幸运,也许那行代码有一个不好的理由。通常,您需要查看多个同时出现的行以找到不好的理由(如果有)。如果 A 在不止一个地方调用 B,则给出的信息就更少了。

  4. 递归是一个棘手和令人困惑的问题。
    这只是因为 gprof 和其他分析器认为需要生成调用图,然后将时间归因给节点。如果有堆栈样本,则出现在样本上的每行代码的时间成本是非常简单的数字 - 它所在样本的比例。如果存在递归,则给定的行可能会出现多次在样本中。 不管怎样,假设每隔 N 毫秒采取一次样本,并且该行出现在 F% 的样本上(单个或不是),则如果该行可以不花费时间(例如通过删除它或跳过它),则这些样本将会消失,并且时间将减少 F%。

  5. 除了其他没有留下说明“为什么”的工作请求方式,例如通过发布消息以外。


3
@Norman: 我在'93年基于这个做了一个 C 语言的性能分析器,运行在 DOS 上。我把它叫做“yet-another-performance-analyzer”,并在 IEEE 的会议上展示过,但就仅此而已。现在 RotateRight 公司有一个名为 Zoom 的产品,类似于我的工具。在 *nix 系统中,pstack 可以手动实现这个功能。我的工作待办清单(在 Windows 上进行药效学研究)已经很长了,没有时间去做一些有趣的项目,更别提家庭了。这个链接或许对你有帮助:https://dev59.com/l0rSa4cB1Zd3GeqPWXGY#1931042 - Mike Dunlavey
6
我总觉得性能分析工具对于修复缓慢代码并不是那么有用,相反我会使用选择性的调试代码来测量我选择的一组语句所花费的时间,通常辅以一些微不足道的小宏或其他东西。我从来没有花太长时间就找到了问题所在,但当“所有人”(就我所知)都使用高级工具时,我总是感到尴尬我的“熊皮和石头刀”的方法。谢谢你向我展示为什么我无法从性能分析工具中获取所需信息。这是我在SO上看到的最重要的想法之一。干得好! - Wayne Conrad
7
@osgx: 我并不是要毁坏任何东西。这就像一辆旧的心爱的汽车,它简单耐用,但有些事情它做不到,我们需要意识到这一点,并且不仅如此,我们还需要从迷思中醒来。我知道在某些平台上获取堆栈样本可能很困难,但如果问题如此复杂以至于gprof解决不了,那么即使它是唯一的工具,也无法提供太多安慰。 - Mike Dunlavey
2
@Andrew:... 而且 如果这个原因适用于一些显著比例的样本(例如超过1个),那么可以消除该活动的代码行就在这些样本上。图表可以给你一个提示,但是不大量的堆栈样本将直接向您展示它们。 - Mike Dunlavey
2
@NormanRamsey:FYI,我刚刚在*sourceforge*上发布了一个更新的示例加速案例(用C++编写)。它包含了所有代码版本和堆栈样本。复合加速因子约为700。 - Mike Dunlavey
显示剩余31条评论

79

Valgrind具有一种名为KCacheGrind的指令计数分析器,并带有一个非常漂亮的可视化工具。正如Mike Dunlavey建议的那样,Valgrind会计算在栈上存活的过程所占指令数的比例,但遗憾的是在存在相互递归时它可能会出现混乱。但是这个可视化工具非常好用,远远超越了gprof


2
@Norman:++递归的混淆似乎在具有图中节点之间传播时间概念的系统中普遍存在。此外,我认为挂钟时间通常比CPU指令时间更有用,代码行(调用指令)比过程更有用。如果在随机挂钟时间取样堆栈,则通过展示它的样本分数来估计一行(或过程或任何其他描述)的分数成本。 - Mike Dunlavey
1
我强调调用指令,但它适用于任何指令。如果有一个真正的热点瓶颈,比如对大量数字进行冒泡排序,那么内部循环的比较/跳转/交换/增量指令将出现在几乎每个堆栈样本的顶部/底部。但是(特别是当软件变得庞大且几乎没有例程具有太多的“自我”时间时),许多问题实际上都是调用指令,请求工作,当清楚知道其成本时,确实不必要完成。 - Mike Dunlavey
3
看这个。我认为他们几乎走在了正确的轨道上:http://www.rotateright.com/zoom.html - Mike Dunlavey

67

由于我在这里没有看到任何有关 perf 的内容,而它是一个相对较新的用于在 Linux 上对内核和用户应用程序进行剖析的工具,因此我决定添加这些信息。

首先 - 这是有关使用 perf 进行 Linux 剖析的教程:Linux profiling with perf

如果您的 Linux 内核版本大于 2.6.32,则可以使用 perf,否则请使用 oprofile。这两个程序都不需要您为程序添加仪表(如 gprof 需要的那样)。但是,为了在 perf 中正确获取调用图,您需要使用 -fno-omit-frame-pointer 构建您的程序。例如:g++ -fno-omit-frame-pointer -O2 main.cpp

您可以使用 perf top 查看您的应用程序的“实时”分析:

sudo perf top -p `pidof a.out` -K

或者,您可以记录正在运行的应用程序的性能数据,然后进行分析:

1)记录性能数据:

perf record -p `pidof a.out`

或者记录10秒钟:

perf record -p `pidof a.out` sleep 10

或者使用调用图()记录

perf record -g -p `pidof a.out` 

2) 分析记录的数据

perf report --stdio
perf report --stdio --sort=dso -g none
perf report --stdio -g none
perf report --stdio -g

您可以通过以如下方式启动应用程序并等待其退出来记录应用程序的性能数据,并在之后进行分析:

perf record ./a.out

这是一个测试程序的分析示例

测试程序位于main.cpp文件中(我将在信息底部放置main.cpp文件):

我以如下方式编译它:

g++ -m64 -fno-omit-frame-pointer -g main.cpp -L.  -ltcmalloc_minimal -o my_test

我使用libmalloc_minimial.so,因为它是使用-fno-omit-frame-pointer编译的,而libc malloc似乎没有使用此选项。 然后我运行我的测试程序。

./my_test 100000000 

然后我记录正在运行的进程的性能数据:

perf record -g  -p `pidof my_test` -o ./my_test.perf.data sleep 30

接下来我会分析每个模块的负载:

perf report --stdio -g none --sort comm,dso -i ./my_test.perf.data

# Overhead  Command                 Shared Object
# ........  .......  ............................
#
    70.06%  my_test  my_test
    28.33%  my_test  libtcmalloc_minimal.so.0.1.0
     1.61%  my_test  [kernel.kallsyms]

然后分析每个函数的负载:

perf report --stdio -g none -i ./my_test.perf.data | c++filt


(注意:保留了原文中的html标签)
# Overhead  Command                 Shared Object                       Symbol
# ........  .......  ............................  ...........................
#
    29.30%  my_test  my_test                       [.] f2(long)
    29.14%  my_test  my_test                       [.] f1(long)
    15.17%  my_test  libtcmalloc_minimal.so.0.1.0  [.] operator new(unsigned long)
    13.16%  my_test  libtcmalloc_minimal.so.0.1.0  [.] operator delete(void*)
     9.44%  my_test  my_test                       [.] process_request(long)
     1.01%  my_test  my_test                       [.] operator delete(void*)@plt
     0.97%  my_test  my_test                       [.] operator new(unsigned long)@plt
     0.20%  my_test  my_test                       [.] main
     0.19%  my_test  [kernel.kallsyms]             [k] apic_timer_interrupt
     0.16%  my_test  [kernel.kallsyms]             [k] _spin_lock
     0.13%  my_test  [kernel.kallsyms]             [k] native_write_msr_safe

     and so on ...

然后分析调用链:

使用以下命令可在图形模式下分析调用链:perf report --stdio -g graph -i ./my_test.perf.data | c++filt

# Overhead  Command                 Shared Object                       Symbol
# ........  .......  ............................  ...........................
#
    29.30%  my_test  my_test                       [.] f2(long)
            |
            --- f2(long)
               |
                --29.01%-- process_request(long)
                          main
                          __libc_start_main

    29.14%  my_test  my_test                       [.] f1(long)
            |
            --- f1(long)
               |
               |--15.05%-- process_request(long)
               |          main
               |          __libc_start_main
               |
                --13.79%-- f2(long)
                          process_request(long)
                          main
                          __libc_start_main

    15.17%  my_test  libtcmalloc_minimal.so.0.1.0  [.] operator new(unsigned long)
            |
            --- operator new(unsigned long)
               |
               |--11.44%-- f1(long)
               |          |
               |          |--5.75%-- process_request(long)
               |          |          main
               |          |          __libc_start_main
               |          |
               |           --5.69%-- f2(long)
               |                     process_request(long)
               |                     main
               |                     __libc_start_main
               |
                --3.01%-- process_request(long)
                          main
                          __libc_start_main

    13.16%  my_test  libtcmalloc_minimal.so.0.1.0  [.] operator delete(void*)
            |
            --- operator delete(void*)
               |
               |--9.13%-- f1(long)
               |          |
               |          |--4.63%-- f2(long)
               |          |          process_request(long)
               |          |          main
               |          |          __libc_start_main
               |          |
               |           --4.51%-- process_request(long)
               |                     main
               |                     __libc_start_main
               |
               |--3.05%-- process_request(long)
               |          main
               |          __libc_start_main
               |
                --0.80%-- f2(long)
                          process_request(long)
                          main
                          __libc_start_main

     9.44%  my_test  my_test                       [.] process_request(long)
            |
            --- process_request(long)
               |
                --9.39%-- main
                          __libc_start_main

     1.01%  my_test  my_test                       [.] operator delete(void*)@plt
            |
            --- operator delete(void*)@plt

     0.97%  my_test  my_test                       [.] operator new(unsigned long)@plt
            |
            --- operator new(unsigned long)@plt

     0.20%  my_test  my_test                       [.] main
     0.19%  my_test  [kernel.kallsyms]             [k] apic_timer_interrupt
     0.16%  my_test  [kernel.kallsyms]             [k] _spin_lock
     and so on ...

现在你知道了程序的时间花费情况。

以下是测试用的 main.cpp 文件:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

time_t f1(time_t time_value)
{
  for (int j =0; j < 10; ++j) {
    ++time_value;
    if (j%5 == 0) {
      double *p = new double;
      delete p;
    }
  }
  return time_value;
}

time_t f2(time_t time_value)
{
  for (int j =0; j < 40; ++j) {
    ++time_value;
  }
  time_value=f1(time_value);
  return time_value;
}

time_t process_request(time_t time_value)
{

  for (int j =0; j < 10; ++j) {
    int *p = new int;
    delete p;
    for (int m =0; m < 10; ++m) {
      ++time_value;
    }
  }
  for (int i =0; i < 10; ++i) {
    time_value=f1(time_value);
    time_value=f2(time_value);
  }
  return time_value;
}

int main(int argc, char* argv2[])
{
  int number_loops = argc > 1 ? atoi(argv2[1]) : 1;
  time_t time_value = time(0);
  printf("number loops %d\n", number_loops);
  printf("time_value: %d\n", time_value );

  for (int i =0; i < number_loops; ++i) {
    time_value = process_request(time_value);
  }
  printf("time_value: %ld\n", time_value );
  return 0;
}

什么是“stackshot”?它是否与“pstack”的输出有关? - user184968
2
正如我在答案中所提到的,你可以在调试器下运行程序,然后在随机时间按下^C键,捕获堆栈跟踪。1)我认为当你需要分析在客户服务器上运行的程序的性能问题时,这种技术并不实用。2)我不确定在处理不同请求的多线程程序中如何应用这种技术来获取信息,尤其是当整体情况非常复杂时。 - user184968
  1. 它是用于查找您自己的代码中可以修复以加快速度的内容。在客户服务器上,问题很可能在您的代码之外 - 例如基于协议的问题。
  2. 当总体情况复杂时,您需要一种简化它的方法,例如逐个请求类型进行聚焦。要避免的是任何形式的整体方法,而不是看树木而非森林。你必须到达“橡胶与路面相接”的地方。
- Mike Dunlavey
2
关于#1。有时客户会打电话说您的程序运行缓慢。你不能立即说“问题在你的代码之外”,对吧?因为你可能需要一些信息来支持你的观点。在这种情况下,您可能需要对应用程序进行分析。您不能只要求客户启动gdb并按^C并获取调用堆栈。这就是我的观点。这是一个例子http://spielwiese.fontein.de/2012/01/22/a-problem-with-pthread_once-on-an-out-dated-solaris-installation。我遇到了这个问题,分析帮了很大的忙。 - user184968
2
关于第2点,简化是一个很好的方法,我同意。有时候它起作用。如果性能问题只出现在客户的服务器上,并且你无法在自己的服务器上复现这些问题,那么分析报告是有用的。 - user184968
显示剩余3条评论

24

尝试使用OProfile。它是一个更好的用于分析代码性能的工具。我还建议使用英特尔的VTune

上述两种工具可以缩小特定代码行的时间范围,为您的代码添加注释,显示汇编和特定指令所需的时间。除了时间测量之外,您还可以查询特定计数器,例如缓存命中率等。

与gprof不同,您可以使用这两种工具之一来分析在系统上运行的任何进程/二进制文件。


2
正如在valgrind答案中提到的那样,来自RotateRight(http://www.rotateright.com)的Zoom提供了更好的界面,并允许远程分析。 - JanePhanie
@Matt 有任何特定的点要提出吗? - Anycorn
它无法处理超过10秒的执行时间,就会产生状态溢出,输出结果并不特别有用,而且文档也很糟糕。 - Matt Joiner
1
@Tho OProfile: ARM、POWER、ia64等。 - Anycorn
OProfile不再维护,已在Debian中被perf替代。 - rurban
显示剩余2条评论

19

Google性能工具包括一个易于使用的性能分析器。提供了CPU和堆分析器。


1
他们现在在 https://github.com/gperftools/gperftools。 - thoni56

8

请看Sysprof

你的发行版可能已经包含了它。


sysprof 生成的输出几乎没有用处,并且很难阅读。 - Matt Joiner

4

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