用gprof
和kcachegrind
分析一些C++数字运算代码,对于对执行时间贡献最大的函数(50-80%取决于输入),会得到类似的结果,但对于10-30%之间的函数,这两种工具会给出不同的结果。这是否意味着其中一个不可靠?你会在这里做什么?
gprof实际上是相当原始的。以下是它的作用。1) 它以恒定的速率对程序计数器进行采样,并记录每个函数中的采样数量(不包括时间)。2) 它计算任何函数A调用任何函数B的次数。从中可以发现每个函数总共被调用了多少次,以及它的平均独占时间是多少。为了获得每个函数的平均包含时间,它在调用图中向上传播独占时间。
如果你期望这有某种准确性,你应该意识到一些问题。首先,它只计算进程中的CPU时间,这意味着它对I/O或其他系统调用是盲目的。其次,递归混淆了它。第三,函数始终遵循平均运行时间的前提是非常可疑的,无论何时调用函数或由谁调用函数。第四,函数(及其调用图)是你需要了解的,而不是代码行,这只是一个流行的假设,仅此而已。第五,测量精度甚至与发现"瓶颈"相关的概念也是一个流行的假设,仅此而已。
Callgrind可以在线条级别工作-这很好。不幸的是,它也存在其他问题。
如果你的目标是找到"瓶颈"(而不是获得一般的测量结果),你应该看看按线报告百分比的挂钟时间堆栈采样器,比如Zoom。原因很简单,但可能并不熟悉。
假设你有一个程序,其中有一堆函数相互调用,总共需要10秒。此外,还有一个采样器,它不仅对程序计数器进行采样,而且对整个调用堆栈进行采样,并且一直以恒定的速率进行采样,比如每秒100次。(暂时忽略其他进程。)
因此,最后您有1000个调用堆栈的样本。选择出现在其中多个代码行上的任意一行代码L。假设您可以以某种方式优化该行,方法是避免它,删除它,或者将它传递给一个非常非常快的处理器。
这些样本会发生什么?
由于这行代码L现在(基本上)根本不需要时间,没有样本可以命中它,所以这些样本将消失,从而减少样本总数,从而减少总时间!事实上,总时间将减少L在堆栈上的时间分数,这大致是包含它的样本的分数
我不想太统计,但很多人认为你需要很多样本,因为他们认为测量的准确性很重要。事实并非如此,如果你这样做的原因是为了找出解决方案来加快速度。重点是找到要修复的内容,而不是测量。行L在堆栈上的某个分数F的时间,对吧?所以每个样本都有一个命中它的概率F,对吧?就像扔硬币一样。有一种理论称之为继承规则。它说(在简化但一般的假设下),如果你把一枚硬币翻N次,看到"头"S次,你可以估计硬币F的公平性为(平均)(S+1)/(N+2)
。所以,如果你只取三个样本,并在其中两个然不是但你确实知道平均值是(2+1)/(3+2)或60%。因此,这就是通过"优化"L行可以(平均)节省的时间。当然,堆栈样本向您显示了行L("瓶颈"**)所在的位置。你没有把它测量到小数点后两三位,这真的重要吗?
顺便说一句,它对上述所有其他问题都免疫。
**我不断引用"瓶颈",因为导致大多数软件速度慢的原因与瓶颈没有任何共同之处。一个更好的比喻是"消耗"——这只是不必要地浪费时间。
gprof
的计时数据是统计数据(请参阅评测文档的详细信息)。
另一方面,KCacheGrind
使用实际上解释所有代码的valgrind
。
因此,如果由valgrind
建模的CPU接近实际CPU,KCacheGrind
可以"更准确"(以牺牲更多开销为代价)。
选择哪一个还取决于你能处理的开销类型。根据我的经验,gprof
添加的运行时开销(即执行时间)较少,但它更具侵入性(即-pg
为每个函数添加代码)。因此,根据情况,开还是另一个更合适。
要获得"更好"的gprof
数据,请长时间运行代码(并尽可能广泛地使用测试数据)。你拥有的越多,统计数据就越好。