不使用该函数作为输入的时间和时间。或退出,但是对于函数A调用任何函数b,它使用在每个例程中计数pc样本所收集的自时间,然后使用函数对函数的调用计数来估计应该向调用方收取多少自时间。
例如,如果A调用C 10次,B调用C 20次,C有1000 ms的自时间(即100 PC示例),那么格罗夫知道C已经被调用了30次,其中33个样本可以被充电到A,而其余的67个可以被充电到B。同样,样本计数在调用层次结构上传播。
所以你看,它没有时间函数的输入和退出。它确实得到的测量非常粗糙,因为它没有区分短呼叫和长呼叫。此外,如果PC示例发生在I/O期间或未使用-pg编译的库例程中,则根本不计算。而且,正如您所提到的,在存在递归的情况下,它是非常脆弱的,并且可以在短函数上引入显著的开销。
另一种方法是堆栈抽样,而不是PC采样.诚然,捕获堆栈样本比捕获PC示例要昂贵得多,但所需的样本更少。例如,如果一个函数、代码行或您想要做的任何描述在N个样本总数中的分数F上是明显的,那么您就知道它所花费的时间的分数是F,标准差为sqrt(NF(1-F)。因此,例如,如果你取100个样本,其中50个样本上出现一行代码,那么你可以估计这一行的成本是50%,不确定度为(100*.5*5)=+/-5样本,或者在45%到55%之间。如果你取了100倍的样品,你可以把不确定度降低10倍。)递归不重要。如果一个函数或代码行在一个样本中出现了3次,那就算作一个示例,而不是3个。函数调用短也没有关系-如果调用它们的次数足够大,那么它们就会被捕获。)
请注意,当您正在寻找可以得到加速的东西时,确切的百分比并不重要。重要的是找到它。(事实上,你只需要看到一个问题两次要知道它足够大,足以修复。)
那是这种技术.
不要被骗到呼叫图、热路径或热点。这是典型的呼叫图鼠巢。黄色是热点,红色是热点.
这表明,在任何一个这样的地方,多汁的加速机会是多么的容易:
最有价值的东西是十几个随机的原始堆栈样本,并将它们与源代码相关联。(这意味着绕过分析器的后端。)
补充:为了说明我的意思,我从上面的调用图中模拟了10个堆栈样本,下面是我发现的
因此,堆栈示例不仅告诉您一个函数或代码行的包含时间花费了多少,它们还告诉您为什么要这样做,以及完成它所需要的愚蠢程度。我经常看到这种飞驰一般的苍蝇用锤子打苍蝇,不是故意的,而是遵循良好的模块化设计。
补充道:另一件不被卷入的事情是火焰图..例如,下面是上面调用图中的10个模拟堆栈样本的火焰图(旋转90度)。例程都是编号的,而不是命名的,但是每个例程都有自己的颜色。
注意上面我们发现的问题,类存在(例程219)在30%的样本上,从火焰图上看,一点也不明显。更多的样本和不同的颜色将使图形看起来更“火焰般的”,但不公开例程,这需要花费大量的时间从不同的地方多次调用。
下面是按函数而不是按时间排序的相同数据。这有点帮助,但并不能汇总来自不同地方的相似之处:
再一次,我们的目标是找出隐藏在你面前的问题。任何人都可以找到简单的东西,但隐藏的问题是那些使一切不同的问题。
补充说:另一种眼睛糖果是这个:
在这里,黑色勾勒的例可能都是一样的,只是从不同的地方调用。图表不为您聚合它们。如果一个例行公事有很高的包容率,被多次从不同的地方调用,它将不会暴露。