gprof的替代品

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

Valgrind有一个指令计数分析器,有一个非常好的名为KCacheGrind的可视化器。 正如迈克·邓拉维(Mike Dunlavey)所build议的那样,Valgrind计算了一个程序在栈上运行的部分指令,尽pipe我很抱歉地说在相互recursion的情况下它似乎变得混乱了。 但是可视化器在gprof之前是非常好的和光年的。

由于历史原因, gprof (阅读文章)存在。 如果你认为它会帮助你发现性能问题,那么它从来没有被广告过。 以下是这篇文章的内容:

该configuration文件可用于比较和评估各种实施的成本。

它并没有说它可以用来识别各种不同的应用程序,但它确实意味着在特殊的情况下:

特别是如果程序的一小部分被发现主宰它的执行时间。

那么那些本地化的问题呢? 这些不重要吗? 不要把期望放在从未声称过的gprof上。 它只是一个测量工具,只有CPU绑定操作。

试试这个。
这是一个44倍速的例子。
这是一个730倍的加速。
这是一个8分钟的video演示。
这里是对统计的解释。
这是批评的答案。

关于程序有一个简单的观察。 在一个给定的执行过程中,每个指令都负责整个时间的一小部分(尤其是call指令),因为如果不在那里,时间将不会被使用。 在那段时间里,指令在堆栈**上。 当明白这一点时,你可以看到 –

gprof体现了一些关于performance的神话,比如:

  1. 程序计数器采样是有用的。
    只有当你有一个不必要的热点瓶颈时才有用,比如一大堆标量值的冒泡sorting。 例如,一旦使用string比较将其更改为sorting,它仍然是一个瓶颈,但程序计数器采样不会看到它,因为现在热点在string比较中。 另一方面,如果要对扩展程序计数器(调用堆栈)进行采样,则会清楚地显示调用string比较的那个点(sorting循环)。 事实上, gprof是一个试图弥补个人电脑采样的限制。

  2. 定时function比捕获耗时的代码行更重要。
    这个神话的原因是gprof无法捕获堆栈样本,所以相反,它的function,计数他们的调用,并试图捕获调用图。 然而,一旦确定了一个代价高昂的function,你仍然需要在内部查找负责时间的行。 如果有堆栈样本,你不需要看,这些线将在样本上。 (一个典型的函数可能有100 – 1000个指令,一个函数调用是1个指令,因此定位代价高昂的调用的精确度要高出2-3个数量级。)

  3. 通话图很重要。
    你需要知道的关于一个程序的地方不是花费时间,而是为什么 。 当它在一个函数中花费时间时,堆栈中的每一行代码都会给出为什么它存在的推理链中的一个链接。 如果只能看到堆栈的一部分,你只能看到部分原因,所以你不能确定是否真的有必要。 通话图表告诉你什么? 每个弧告诉你一些函数A正在调用一些函数B的一小部分时间。 即使A只有一个这样的代码行调用B,该行只给出了一小部分原因。 如果你足够幸运,也许这条线有一个不好的原因。 通常情况下,你需要看到多条同时发生的线路,如果它在那里find一个不好的原因。 如果A在多个地方打电话给B,那么它告诉你更less。

  4. recursion是一个棘手的混淆问题。
    这只是因为gprof和其他分析人员认为需要生成一个调用图,然后将时间归入节点。 如果有堆栈的样本,那么样本上出现的每一行代码的时间成本就是一个非常简单的数字 – 它所在样本的一小部分。 如果有recursion,那么一个给定的行可以出现在一个样本上不止一次。 不pipe。 假设每N ms采样一次,并且这一行出现在F%(单独或不单独)。 如果这条路线可以不花时间(如删除或分支),那么这些样本就会消失 ,时间会减lessF%。

  5. 时间测量的准确性(以及因此大量的样本)是重要的。
    仔细想想一下。 如果一行代码是5个样本中的3个样本,那么如果你能像灯泡那样把它射出来,那么这个时间就会less60%。 现在,你知道,如果你采取了不同的5个样本,你可能只看过2次,或多达4个。所以60%的测量更像是一个40%到80%的一般范围。 如果只有40%,你会说这个问题不值得修复吗? 那么,什么时候准确,什么时候你真正想要的是find问题 ? 500或5000个样本本可以以更高的精确度来测量这个问题,但是不会更准确地发现它。

  6. 计数语句或函数调用是有用的。
    假设你知道一个函数被称为1000次。 你能告诉我们,这个时间花费了多less时间? 您还需要知道平均运行需要多长时间,然后除以总时间。 平均调用时间可能会在几纳秒到几秒之间变化,所以仅靠计数并不能说明问题。 如果有堆栈样本,例程或任何语句的成本只是它所在样本的一小部分。 这样的时间是原则上可以节省下来的,如果日常工作或声明可以不花时间的话,那么这就是与绩效最直接的关系。

  7. 当被阻塞时不需要采样
    这个神话的原因是两方面的:1)程序在等待时,PC采样是没有意义的; 2)对时序准确性的关注。 但是,对于(1)程序可能正在等待它所要求的内容,例如你需要知道的文件I / O,以及哪个堆栈样本显示。 (很明显,你希望在等待用户input的时候排除样本。)(2)如果程序因为与其他进程竞争而等待,这可能是在运行时以相当随机的方式发生的。 因此,虽然程序可能会花费更长的时间,但对于重要的统计信息(即语句在堆栈中的时间百分比)不会有太大的影响。

  8. “自我时间”很重要
    自我时间只有在function级别而不是线路级别测量时才有意义,并且您认为您需要帮助来辨别function时间是否进入纯粹的本地计算而不是所谓的例程。 如果在行级进行汇总,如果行在堆栈的末尾,则表示自我时间,否则表示包含时间。 无论哪种方式,它所花费的是堆栈样本的百分比,以便在任何情况下都能find它。

  9. 样品必须以高频率拍摄
    这是因为性能问题可能是快速行动的,而且样本必须频繁才能实现。 但是,如果问题花费了20%,比如10秒(或其他)的总运行时间,那么在这个总时间中的每个样本将有20%的机会击中它,不pipe问题是否发生就像这样
    .....XXXXXXXX...........................
    (20个样本,4个命中) .^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^
    或者像这样的许多小片段
    X...X...XX.X.........X.....X....X.....
    (20个样本,3个点击),
    无论哪种方式,无论采取多less样本,或者有多less样本,命中的数量平均约为1/5。 (平均值= 20 * 0.2 = 4,标准偏差= +/- sqrt(20 * 0.2 * 0.8)= 1.8)

  10. 你正试图find瓶颈
    仿佛只有一个。 考虑以下执行时间表: vxvWvzvWvxvWvYvWvxvWv.vWvxvWvYvW
    它由真正有用的工作组成,代表着. 。 性能问题vWxYz取1/2,1 / 4,1 / 8,1 / 16,1 / vWxYz的时间。 抽样很容易findv 。 它被删除,离开
    xWzWxWYWxW.WxWYW
    现在程序运行时间只有一半,现在W占用了一半的时间,很容易find。 它被删除,离开
    xzxYx.xY
    这个过程继续下去,每次删除最大的百分比性能问题,直到找不到任何可以删除的东西。 现在唯一执行的是. ,它在原始程序使用的1/32时间内执行。 这是放大效应 ,通过去除任何问题使余数更大,因为分母减less了。
    另一个关键的问题是必须find每一个问题 – 没有任何一个都没有find。任何没有发现和解决的问题都会严重降低最终的加速比。 只是find一些,但不是全部,是不够“好”。

补充:我只想指出gprof受欢迎的一个原因 – 它正在被教导,大概是因为它是免费的,易于教导的,而且已经有很长一段时间了。 快速的Googlesearchfind一些教授它(或看起来)的学术机构:

伯克利布克莱姆森科罗拉多公爵伯爵夫人厄尔汉姆福克斯印第安纳姆msu ncsa.illinois ncsu nyu ou普林斯顿psu斯坦福ucsd umd umich犹他utexas utk wustl

**除了要求完成工作的其他方式之外,不要留下跟踪信息,例如通过邮件发送。

由于我没有看到有关perf一切,这是一个在Linux上分析内核和用户应用程序的相对较新的工具,我决定添加这些信息。

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

如果您的Linux内核版本高于2.6.32,则可以使用perf如果旧版本则可以使用oprofile 。 这两个程序不需要你来testing你的程序(如gprof要求)。 但是为了在perf正确地获取调用图,需要使用-fno-omit-frame-pointer构build程序。 例如: g++ -fno-omit-frame-pointer -O2 main.cpp

你可以看到你的应用程序的“实时”分析:

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

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

1)logging性能数据:

 perf record -p `pidof a.out` 

或logging10秒钟:

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

或者用调用图来logging()

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

2)分析logging的数据

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

或者您可以logging应用程序的性能数据,然后通过以这种方式启动应用程序并等待它退出来分析它们:

 perf record ./a.out 

这是分析testing程序的一个例子

testing程序在文件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似乎是在没有这个选项的情况下编译的。 然后我运行我的testing程序

 ./my_test 100000000 

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

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

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

perf报告–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] 

然后分析每个function的负载:

perf报告–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报告–stdio -ggraphics-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 ... 

所以在这一点上,你知道你的程序在哪里花费时间。

这是testing的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; } 

尝试OProfile 。 这是分析代码更好的工具。 我也会build议英特尔VTune 。

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

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

Google性能工具包括一个简单易用的分析器。 CPU以及堆分析器可用。

看看Sysprof 。

你的发行版可能已经有了。