教授的替代品

还有哪些程序和 gprof 做同样的事情?

79516 次浏览

尝试 侧写。这是一个更好的工具来分析你的代码。我也建议英特尔 VTune

上面的两个工具可以缩短花费在特定代码行上的时间,为代码加注释,显示汇编以及特定指令所占用的时间。除了时间度量,您还可以查询特定的计数器,比如缓存命中等等。

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

由于历史原因,gprof (看报纸)存在。 如果您认为它可以帮助您发现性能问题,那么它从来没有像这样做过广告。 报纸是这么说的:

该概况可用于比较和评估成本 各种实施方案。

它没有说它可以用来评估 确认身份的各种执行情况,尽管它确实说它可以在特殊情况下:

特别是如果发现程序的一小部分主导了它的 行刑时间。

那些不是局部性的问题呢? 那些不重要吗? 不要对 教授寄予期望,因为它从未被认可。 它是 只有的一个测量工具,并且只对 CPU 有限制的操作。

试试这个。
这里有一个44倍加速的例子。
这是730倍加速。
这是一个8分钟的视频演示。
下面是统计数据的解释。
以下是对批评的回应

对于程序有一个简单的观察。在一个给定的执行中,每条指令占用总时间的一部分(特别是 call指令) ,因为如果没有指令,时间就不会被花费。在此期间,指令位于堆栈 * * 上。当这被理解,你可以看到-

Gprof 体现了某些关于绩效的神话,例如:

  1. 程序计数器采样是有用的。
    只有当你有一个不必要的热点瓶颈时才有用,比如一个大的标量值数组。例如,一旦您使用字符串比较将其更改为排序,它仍然是一个瓶颈,但程序计数器采样将不会看到它,因为现在热点是在字符串比较。另一方面,如果要对 延期程序计数器(调用堆栈)进行采样,则清楚地显示调用字符串比较的点,即排序循环。事实上,< strong > gprof 是试图纠正只有 pc 采样的局限性。

  2. 计时函数比捕获耗时的代码行更重要。
    这个误区的原因是 教授无法捕获堆栈样本,因此它计算函数的时间,计算它们的调用,并尝试捕获调用图。但是,一旦确定了代价高昂的函数,您仍然需要在其中查找负责时间的行。如果存在您不需要查看的堆栈样本,那么这些行将出现在样本上。(一个典型的函数可能有100-1000条指令。函数 打电话是1条指令,因此定位代价高昂的调用的数量级要精确2-3倍。)

  3. 调用图很重要。
    关于一个程序,你需要知道的不是它花费时间的 哪里,而是 为什么。当它在一个函数中花费时间时,堆栈上的每一行代码都会在推理链中提供一个链接,说明它为什么存在。如果您只能看到堆栈的一部分,那么您只能看到部分原因,因此您无法确定是否确实需要这段时间。 调用图告诉你什么?每个弧都告诉您,某个函数 A 在某个时间段内调用某个函数 B。即使 A 只有一行这样的代码调用 B,这一行只给出了原因的一小部分。如果你足够幸运,也许这句台词有一个可怜的理由。通常,您需要查看多个同时发生的行,以找到一个不好的理由,如果它存在的话。如果 A 在不止一个地方调用 B,那么它告诉你的就更少了。

  4. 递归是一个棘手的令人困惑的问题。
    这仅仅是因为 教授和其他分析器感觉到需要生成一个调用图,然后为节点分配时间。如果有一个堆栈的样本,那么出现在样本上的每一行代码的时间成本是一个非常简单的数字——它所在的样本的分数。如果有递归,那么给定的行可以在示例上出现多次。 没关系。假设每 N 毫秒采样一次,其中 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. 必须以高频率采集样本
    这来自于这样一个想法,即性能问题可能是快速反应的,并且为了解决它,样本必须是频繁的。但是,如果问题是成本,20% ,说,总运行时间为10秒(或其他) ,那么每个样品在总时间将有20% 的机会击中它,不管问题是否发生在一个单一的一块像这样
    .....XXXXXXXX...........................
    .^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^(20个样本,4个匹配)
    或者像这样的小碎片
    X...X...X.X..X.........X.....X....X.....
    .^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^(20个样本,3个匹配)
    无论采用哪种方法,无论采集多少样本,或者采集的样本数量有多少,命中的次数平均约为五分之一。(平均数 = 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个。任何未发现和修复的问题都会严重降低最终的加速比。仅仅找到一些,但不是全部,是不够的。

补充: 我只想指出一个原因为什么 教授是流行的-它是被教, 大概是因为它免费,容易教,而且已经存在很长时间了。 一个快速的谷歌搜索可以找到一些教授它的学术机构(或者看起来是这样的) :

克莱姆森大学伯克利分校 科罗拉多州,印第安纳州,厄尔勒姆公爵 伊利诺斯州立大学或普林斯顿大学 斯坦福大学,犹他州尤特克萨斯大学

* * 除了其他请求完成工作的方式外,这些方式不会留下通知 为什么的痕迹,例如通过邮件发送。

Valground 有一个指令计数分析器,它有一个非常好的可视化工具 KCacheGrind。正如 Mike Dunlavey 建议的那样,尽管我很遗憾地说,在存在相互递归的情况下,它似乎变得很混乱,但 Val弓还是计算了堆栈上存在过程的指令的分数。但可视化是非常好的和光年之前的 gprof

看看 Sysprof

你的分销商可能已经有了。

谷歌性能工具 包括一个简单的使用分析器。 CPU 和堆分析器是可用的。

因为我在这里没有看到任何关于 perf的东西,它是一个相对较新的工具,用于分析 Linux 上的内核和用户应用程序,所以我决定添加这些信息。

首先-这是一个关于 使用 perf进行 Linux 分析的教程

如果 Linux 内核大于2.6.32,可以使用 perf; 如果 Linux 内核更旧,可以使用 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

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