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