为了账号安全,请及时绑定邮箱和手机立即绑定

用_gnu_mcount_nc捕获函数退出时间

用_gnu_mcount_nc捕获函数退出时间

C++
墨色风雨 2019-07-12 15:33:50
用_gnu_mcount_nc捕获函数退出时间我试图在一个不受支持的原型嵌入式平台上进行一些性能分析。我注意到GCC的-pg旗导致了__gnu_mcount_nc在输入到每个函数时插入。没有执行__gnu_mcount_nc是可用的(供应商对协助不感兴趣),但是,由于编写一个只记录堆栈帧和当前循环计数的程序非常简单,我已经这样做了;这样做很好,并且在调用者/被调用方图和最常被称为函数的情况下产生了有用的结果。我真的很想获得关于在函数体中花费的时间的信息,但是我很难理解如何仅用入口(而不是出口)来处理每个函数的连接:您可以准确地知道每个函数何时输入,但如果不将退出点挂钩,您就无法知道在收到下一个被调用者的信息之前有多少时间,以及调用方需要多少时间。尽管如此,GNU分析工具实际上确实能够为许多平台上的功能收集运行时信息,因此,开发人员想必有一些实现这一目标的方案。我已经看到了一些现有的实现,比如维护影子调用堆栈,并在进入_gnu_mcount_nc时旋转返回地址,以便在被调用方返回时再次调用_gnu_mcount_nc;然后它可以将调用者/callee/sp三元组与影子调用堆栈的顶部相匹配,从而在输入时将此情况与调用区分开来,记录退出时间并正确返回给调用方。这一办法还有许多有待改进的地方:在没有-pg标志的递归和库的存在下,它似乎很脆弱。在缺乏工具链tls支持且当前线程ID可能很昂贵/复杂的嵌入式多线程/多核环境中,似乎很难在低开销的情况下实现。有什么明显的更好的方法来实现a_gnu_mcount_nc,以便-pg构建能够捕获函数退出以及我所缺少的入口时间?
查看完整描述

1 回答

?
杨魅力

TA贡献1811条经验 获得超5个赞

不使用该函数作为输入的时间和时间。退出,但是对于函数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个堆栈样本,下面是我发现的

  • 3/10样品正在打电话

    class_exists

    ,一个用于获取类名,两个用于设置本地配置。

    class_exists

    打电话

    autoload

    打电话

    requireFile

    ,其中两个电话

    adminpanel

    ..如果能更直接地做到这一点,就可以节省大约30%。
  • 2/10的样品正在打电话

    determineId

    ,这就叫

    fetch_the_id

    打电话

    getPageAndRootlineWithDomain

    ,它再调用三个级别,终止于

    sql_fetch_assoc

    ..这似乎需要花费20%的时间才能获得身份证,而这还不包括I/O。

因此,堆栈示例不仅告诉您一个函数或代码行的包含时间花费了多少,它们还告诉您为什么要这样做,以及完成它所需要的愚蠢程度。我经常看到这种飞驰一般的苍蝇用锤子打苍蝇,不是故意的,而是遵循良好的模块化设计。

补充道:另一件不被卷入的事情是火焰图..例如,下面是上面调用图中的10个模拟堆栈样本的火焰图(旋转90度)。例程都是编号的,而不是命名的,但是每个例程都有自己的颜色。
注意上面我们发现的问题,类存在(例程219)在30%的样本上,从火焰图上看,一点也不明显。更多的样本和不同的颜色将使图形看起来更“火焰般的”,但不公开例程,这需要花费大量的时间从不同的地方多次调用。

下面是按函数而不是按时间排序的相同数据。这有点帮助,但并不能汇总来自不同地方的相似之处:
再一次,我们的目标是找出隐藏在你面前的问题。任何人都可以找到简单的东西,但隐藏的问题是那些使一切不同的问题。

补充说:另一种眼睛糖果是这个:
在这里,黑色勾勒的例可能都是一样的,只是从不同的地方调用。图表不为您聚合它们。如果一个例行公事有很高的包容率,被多次从不同的地方调用,它将不会暴露。


查看完整回答
反对 回复 2019-07-12
  • 1 回答
  • 0 关注
  • 358 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信