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