gprof的替代品


166

还有哪些其他程序与gprof做相同的事情?


2
您对哪些平台感兴趣?
osgx

2
我对Linux感兴趣。
Neuromancer 2010年


13
@Gregory-我倾向于表示同意,也许他应该以自己的答案229 vs 6做出贡献,所有这些答案中的6个都是针对他自己的问题...
Jean-Bernard Pellerin 2010年

5
这个问题怎么不会是建设性的?
JohnTortugo 2013年

Answers:


73

Valgrind有一个指令计数探查器,带有一个非常好的可视化器,称为KCacheGrind。正如Mike Dunlavey所建议的那样,Valgrind会计算过程中存在于堆栈中的指令所占的比例,尽管我很遗憾地说,在存在相互递归的情况下,该过程似乎很混乱。但是可视化器非常好,提前了几年gprof


2
@Norman:++对递归的困惑似乎是普遍存在的系统,这些系统具有在图中的节点之间传播时间的概念。我还认为,挂钟时间通常比CPU指令时间更有用,而代码行(调用指令)比过程更有用。如果在随机的墙上时钟时间获取堆栈样本,则仅通过展示该行的样本所占的比例来估算生产线(或过程或您可以进行的任何其他描述)所占的比例成本。
Mike Dunlavey,

1
...我在强调通话说明,但是它适用于任何说明。如果有一个诚实到善的热点瓶颈,例如大量数字组成的气泡,那么内部循环的比较/跳转/交换/递增指令将位于几乎每个堆栈样本的顶部/底部。但是(尤其是软件变大,几乎没有任何程序有很大的“自我”的时间)的许多问题实际上是调用指令,要求工作,当它是清楚它花了多少钱,并没有真正有许多工作要做。
Mike Dunlavey,

3
... 看一下这个。我认为他们几乎走在正确的轨道上:rotationright.com/zoom.html
Mike Dunlavey,2009年

195

gprof (阅读本文)存在是出于历史原因。如果您认为它可以帮助您发现性能问题,则永远不会这样宣传。这篇文章说的是:

该配置文件可用于比较和评估各种实现的成本。

它没有说它可以用来识别要评估的各种实现,尽管它暗示它可以在特殊情况下:

尤其是在发现程序的一小部分控制其执行时间的情况下。

那不是那么本地化的问题呢?那些没关系吗?不要对从来没有要求过的gprof寄予期望。它是一种测量工具,并且仅用于受CPU约束的操作。

试试这个吧。
这是一个44倍加速的示例。
这是730倍的加速比。
这是一个8分钟的视频演示。
这是统计信息的解释。
这是批评的答案。

关于程序有一个简单的观察。在给定的执行中,每条指令负责整个时间的一部分(尤其是call指令),从某种意义上说,如果不存在,则不会花费时间。在此期间,指令位于堆栈**中。了解这一点后,您可以看到-

gprof体现了有关性能的某些神话,例如:

  1. 程序计数器采样很有用。
    仅当您有不必要的热点瓶颈(例如大量标量值的冒泡类型)时才有用。例如,一旦您使用string-compare将其更改为某种类型,它仍然是一个瓶颈,但是程序计数器采样不会看到它,因为现在热点在string-compare中。另一方面,如果要对扩展程序计数器(调用堆栈)进行采样,则会清晰显示调用字符串比较的点(排序循环)。实际上,gprof旨在弥补仅PC采样的局限性。

  2. 计时功能比捕获费时的代码行更重要。
    产生这种说法的原因是gprof无法捕获堆栈样本,因此它对函数进行计时,计算其调用次数并尝试捕获调用图。但是,一旦确定了一项昂贵的功能,您仍然需要在其内部查找造成该时间的线路。如果有堆栈样本,则无需查看,这些行将在样本上。(一个典型的函数可能有100-1000条指令。一个函数调用是1条指令,因此定位昂贵调用的函数要精确2-3个数量级。)

  3. 调用图很重要。
    您需要了解的程序不是花时间在哪里,而是为什么。当花时间在一个函数上时,堆栈中的每一行代码都会在其原因推理链中给出一个链接。如果您只能看到堆栈的一部分,那么只能看到部分原因,因此无法确定是否确实需要该时间。通话图会告诉您什么?每条弧线都告诉您某些函数A在一段时间内正在调用某些函数B。即使A只有一行这样的代码调用B,该行也仅给出了原因的一小部分。如果您足够幸运,也许那条线没有充分的理由。通常,您需要查看多条并发的行以找到不良原因(如果存在)。如果A呼叫B的地点多于一个,那么它告诉您的甚至更少。

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

  5. 时间测量的准确性(因此需要大量样本)非常重要。
    想一想。如果一行代码位于五个样本中的三个样本上,那么如果您可以像灯泡一样将其发射出去,则可以节省大约60%的时间。现在,您知道,如果您另取5个样本,则可能只看过2次,或多达4次。因此60%的测量更像是从40%到80%的一般范围。如果只有40%,您是否认为这个问题不值得解决?那么,当您真正想要的是发现问题时,时间准确性的重点是什么?500或5000个样本本可以以更高的精度测量问题,但找不到更准确的问题。

  6. 计数语句或函数调用很有用。
    假设您知道某个函数已被调用1000次。您能从中得知花费多少时间吗?您还需要知道平均需要运行多长时间,然后将其乘以计数,再除以总时间。平均调用时间可能在纳秒到几秒之间变化,因此仅凭计数并不能说明什么。如果有堆栈样本,那么例程或任何语句的成本只是它所使用的样本的一小部分。如果可以使例程或语句不花时间,那么原则上可以节省整个时间,这是与性能最直接的关系。

  7. 该封端当样品不必采取
    这种神话的原因是双重的:1)PC采样是无意义的,当程序处于等待状态,以及2)与定时准确度的当务之急。但是,对于(1),程序很可能在等待它所要求的内容,例如文件I / O,您需要知道这些内容,以及哪些堆栈样本可以显示。(显然,您希望在等待用户输入时排除样本。)对于(2),如果程序仅由于与其他进程的竞争而在等待,那么大概是在运行时以相当随机的方式发生。因此,尽管程序可能花费更长的时间,但这不会对重要的统计数据产生太大的影响,而是语句在堆栈上的时间百分比。

  8. “自我时间”很重要
    自我时间仅在您在功能级别(而不是线路级别)进行测量时才有意义,并且您认为在识别功能时间是否用于纯本地计算而非调用例程中时,您需要帮助。如果在行级别进行汇总,则行表示自身时间(如果它位于堆栈的末尾),否则代表包含时间。不管哪种方式,它的成本都是它在堆栈样本上的百分比,因此无论哪种情况都可以找到。

  9. 必须以高频率进行采样
    这是基于这样的想法,即性能问题可能正在迅速解决,并且必须频繁采样才能达到目标。但是,如果问题耗费成本,例如,在10秒(或任何其他时间)的总运行时间中占20%,则在此总时间内的每个样本都将有20%的机会实现目标,无论是否发生问题 无论是像这样的单个片段
    .....XXXXXXXX...........................
    .^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^(20个样本,4个匹配),
    或者像这样的许多小片段
    X...X...X.X..X.........X.....X....X.....
    .^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^(20个样本,3个匹配),
    无论采用多少个样本,匹配的次数平均为5分之一。几个 (平均值= 20 * 0.2 =4。标准偏差= +/- sqrt(20 * 0.2 * 0.8)= 1.8。)

  10. 你正在努力寻找瓶颈
    好像只有一个有。请考虑以下执行时间表:vxvWvzvWvxvWvYvWvxvWv.vWvxvWvYvW
    它由表示有用的实际工作组成.。在分别vWxYz花费1 / 2、1 / 4、1 / 8、1 / 16、1 / 32的时间存在性能问题。采样v容易找到。它已被删除,离开了。
    xWzWxWYWxW.WxWYW
    现在程序的运行W时间是原来的一半,而现在的时间是原来的一半,而且很容易找到。它被删除,离开
    xzxYx.xY
    此过程继续进行,每次删除百分比最大的性能问题,直到找不到要删除的内容。现在,唯一执行的是.,它执行的时间是原始程序使用时间的1/32。这是放大效果,由于分母减少了,因此消除任何问题都会使其余的百分比增加。
    另一个关键点是,必须找到每个问题 -都不遗漏5个。未找到并修复的任何问题都会严重降低最终的加速比。仅找到一些但不是全部,还不够好。

新增:我只想指出gprof受欢迎的原因之一-有人在教它,大概是因为它是免费的,容易教的,而且已经存在了很长时间。通过Google的快速搜索,可以找到一些教过它(或看起来如此)的学术机构:

伯克利bu克莱姆森科罗拉多杜克厄尔勒姆fsu印第安纳州密西西比州ncsa.illinois ncsu nyu ou princeton psu斯坦福ucsd umd umich utah utxas utk wustl

**除要求完成工作的其他方式外,不要留下任何告诉原因的痕迹,例如通过消息发布。


3
@Norman:基于此,我在93年左右的C for DOS中制作了一个探查器。我称它为“另一性能分析器”,并在IEEE会议上对其进行了演示,但目前为止。RotateRight提供的一种名为Zoom的产品距离不太远。在* nix上,pstack非常适合手动执行。我的工作清单(在Windows上是药理学)大约一英里长,这排除了有趣的项目,更不用说家庭。:这可能是有用的stackoverflow.com/questions/1777669/...
麦克Dunlavey

6
我一直发现分析器对于修复慢速代码不是那么有用,而是使用调试代码的选择性位来衡量我选择的一组语句所花费的时间,通常借助一些琐碎的小宏或其他工具。从来没有花太长时间找到罪魁祸首,但是当“其他所有人”(据我所知)使用这种花哨的工具时,我总是为自己的“熊皮和石刀”方法感到尴尬。感谢您向我展示为什么我永远无法从Profiler获得所需的信息。这是我在SO上看到的最重要的想法之一。做得好!
韦恩·康拉德

7
@osgx:我不是要撕任何东西。这就像一辆古老的最受欢迎的汽车,简单而坚固,但是有些事情它并没有做到,我们需要意识到这些,不仅如此,我们还需要从神话中醒来。我了解在某些平台上可能很难获得堆栈样本,但是如果gprof找不到问题,那么它是唯一的工具,那就是舒适度不高。
Mike Dunlavey 2010年

2
@Andrew:... 并且,如果该原因适用于样本的很大一部分(例如超过1个),那么可以消除该活动的代码行就在这些样本上。一张图可以给您一些提示,但是数量不多的堆栈样本只会向您显示。
Mike Dunlavey

2
@Matt:以这种方式发现的IO性能问题示例:1)将日志消息打印到文件或控制台上,这被错误地认为是无关紧要的。2)在文本和数字IO中的双精度之间进行转换。3)地下IO在启动期间提取国际化的字符串,事实证明,这些字符串不需要国际化。我遇到过很多这样的例子。
Mike Dunlavey 2010年

63

由于我在这里看不到任何关于perf在Linux上对内核和用户应用程序进行性能分析的相对较新的工具,因此我决定添加此信息。

首先-这是关于Linux性能分析的教程perf

perf如果您的Linux内核大于2.6.32或oprofile更旧,则可以使用。这两个程序都不需要您来检测您的程序(就像require一样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-pointerlibc malloc似乎是在没有此选项的情况下编译的。然后我运行测试程序

./my_test 100000000 

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

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

然后,我分析每个模块的负载:

性能报告--stdio -g无--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]

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

性能报告--stdio -g none -i ./my_test.perf.data | C ++过滤

# 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 ...

然后分析呼叫链:

性能报告--stdio -g图-i ./my_test.perf.data | C ++过滤

# 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;
}

我只是运行您的示例并拍摄了5张stackshots。这是他们发现的内容:40%(大约)的时间f1在打电话delete。40%(大约)的时间process_request在打电话delete。其余的很大一部分都花在了new。测量值很粗糙,但是热点是精确的。
Mike Dunlavey

什么是stackshot?是pstack输出吗?

2
As in my answer, you run it under a debugger and hit ^C at a random time and capture the stack trace。1)我认为当您需要分析客户服务器上运行的程序的性能问题时,您的技术没有用。2)我不确定您如何应用此技术来获取程序的信息,该程序具有许多处理不同请求的线程。我的意思是总体情况非常复杂。

2
至于#1。有时客户打电话说您的程序运行缓慢。你不能马上说the problem is outside your code,是吗?因为您可能需要一些信息才能支持您的观点。在这种情况下,您有时可能需要分析您的应用程序。您不能仅仅要求您的客户启动gdb并按^ C并获取调用堆栈。这就是我的意思。这是spielwiese.fontein.de/2012/01/22/…的示例。我遇到了这个问题,分析很有帮助。

2
至于#2。我同意,简化是一个好方法。有时可以。如果仅在客户的服务器上出现性能问题,而您不能在服务器上重现它们,则使用概要文件。

21

试试OProfile。这是用于分析代码的更好的工具。我还建议使用Intel VTune

上面的两个工具可以缩短花在特定代码行上的时间,注释代码,显示汇编以及特定指令需要多少时间。除了时间指标外,您还可以查询特定的计数器,例如缓存命中率等。

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


2
就像在valgrind答案中也提到的那样,RotateRight(rotateright.com)中的Zoom 提供了一个更好的界面,并允许进行远程配置。
JanePhanie 2010年

不喜欢oprofile,这似乎是偶然的
Matt Joiner 2010年

@Matt有什么特别的地方吗?
Anycorn

在生成统计信息溢出之前,它不能应付超过10秒钟的执行时间,输出并不是特别有用,并且文档也很糟糕。
马特·乔纳

1
@Tho OProfile:ARM,POWER,ia64,...
Anycorn



By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.