实现中断驱动的采样分析器

时间:2014-05-12 20:57:48

标签: c++ profiling sampling

我正在尝试创建一个适用于linux的采样分析器,我不确定如何发送中断或如何获取程序计数器(pc)以便我可以找到程序在我中断时的重点。

我尝试过使用signal(SIGUSR1,Foo *)并调用backtrace,但是当我引发(SIGUSR1)而不是正在运行程序的线程时,我得到了我所在线程的堆栈。 我不确定这是否是正确的方式......

有什么建议吗?

2 个答案:

答案 0 :(得分:3)

如果你必须编写一个分析器,我建议你使用一个好的(Zoom)作为你的模型,而不是一个坏的( gprof ) 。 这是它的特点。

有两个阶段。首先是数据收集阶段:

  • 当它需要一个样本时,它会读取整个调用堆栈,而不仅仅是程序计数器。

  • 即使由于I / O,睡眠或其他原因导致进程被阻止,也可以采样。

  • 您可以打开/关闭采样,以便在您关心的时间内只采样。例如,在等待用户输入内容时,抽样是没有意义的。

其次是数据呈现阶段。 你拥有的是堆栈样本的集合,其中堆栈样本是内存地址的向量,几乎都是返回地址。 每个返回地址表示函数中的一行代码,除非它在某些系统例程中没有符号信息。

有用信息的关键部分是驻留分数(通常表示为百分比)。 如果总共有 m 堆栈样本,并且代码行L出现在样本的 n 的任何位置,则其驻留分数 n / m < / em>的。 即使L在样本上出现的次数多一次也是如此,这仍然只是它出现的一个样本。 居住分数的重要性在于它直接表明时间陈述L负责的部分。 如果你已经采用 m = 1000 样本,并且L出现在 n = 300 上,那么L's驻留率为300/1000或30%。 这意味着如果可以删除L,则总时间将减少30%。 它通常称为包含百分比

您不仅可以确定代码行的居住分数,还可以确定您可以描述的其他内容。例如,代码行L在某个函数F内。 因此,您可以确定函数的驻留分数,而不是代码行。 这将为您提供包含功能的百分比。 您可以查看函数对,比如您看到函数F调用函数G的样本分数。 这将为您提供构成调用图的信息。

您可以从堆栈样本中获取各种信息。 经常看到的是“蝴蝶视图”,其中您在一行L或函数F上有一个“焦点”,并且在一侧显示堆栈样本中紧邻其上方的所有行或函数,另一侧紧接着它下方的所有功能线。 在每个上面,您都可以显示居住率。 您可以在此处单击以尝试查找具有高驻留分数的代码行,您可以找到消除或减少的方法。 这就是你加速代码的方式。

无论你做什么输出,我认为允许用户实际检查随机选择的少数这些非常重要。 它们传达的信息远远超过任何凝聚信息的方法。

重要的是要了解分析器应该做什么,知道要做什么也很重要,即使许多其​​他分析器都这样做:

  • 自我时间。一个无用的号码。看一些合理大小的程序,你会明白为什么。

  • 调用计数。找到具有高居住率的代码没有任何帮助,无论如何你无法单独获得样本。

  • 高频采样。令人惊讶的是,有多少人,当然是探查器构建者,认为获得批次样本非常重要。假设线L在1000个样本的30%上。然后它的真实包容百分比是30 +/- 1.4%。另一方面,如果它是 10 样本的30%,则其包含百分比为30 +/- 14%。 它仍然非常大 - 足以修复。在大多数剖析器中发生的事情是人们认为他们需要“数值精度”,因此他们需要大量样本并累积他们所谓的“统计”,然后抛出远离样品。这就像挖掘钻石,称重钻石,扔掉钻石一样。真正的价值在于样本本身,因为它们告诉你问题是什么。

答案 1 :(得分:2)

您可以使用目标线程的pthread_kill和tid(gettid())向特定线程发送信号。

创建简单剖析器的正确方法是使用setitimer,它可以发送周期信号(SIGALRMSIGPROF),例如,每10 ms;或posix定时器(timer_create,timer_settime或timerfd),无需单独的线程来发送分析信号。检查google-perftools(gperftools)的来源,他们使用setitimer或posix计时器并收集带有回溯的配置文件。

gprof还使用setitimer来实现cpu时间分析(9.1 Implementation of Profiling - “Linux 2.0 ..安排内核定期向进程发送信号(通常通过setitimer())” )。

例如:gperftools来源中setitimer的代码搜索结果:https://code.google.com/p/gperftools/codesearch#search/&q=setitimer&sq=package:gperftools&type=cs

void ProfileHandler::StartTimer() {
  if (!allowed_) {
    return;
  }
  struct itimerval timer;
  timer.it_interval.tv_sec = 0;
  timer.it_interval.tv_usec = 1000000 / frequency_;
  timer.it_value = timer.it_interval;
  setitimer(timer_type_, &timer, 0);
}

您应该知道setitimer在forkclone方面存在问题;它不适用于多线程应用程序。尝试创建帮助包装器:http://sam.zoy.org/writings/programming/gprof.html(错误的)但我不remember,它是否正常工作(setitimer通常发送进程范围的信号,而不是线程范围的)。 UPD:似乎自Linux内核2.6.12,setitimer's signal is directed to the process as whole(任何线程都可以得到它)。

要将来自timer_create的信号指向特定线程,您需要gettid()#include <sys/syscall.h>syscall(__NR_gettid))和SIGEV_THREAD_ID flag。不检查如何使用thread_create创建周期性posix计时器(可能使用timer_settime and non-zero it_interval)。

PS:wikibooks中有一些概要分析概述:http://en.wikibooks.org/wiki/Introduction_to_Software_Engineering/Tools/Profiling