实现一个中断驱动的采样分析器
Posted
技术标签:
【中文标题】实现一个中断驱动的采样分析器【英文标题】:Implementing an Interrupt driven Sampling Profiler 【发布时间】:2014-05-12 20:57:47 【问题描述】:我正在尝试创建一个适用于 linux 的采样分析器,我不确定如何发送中断或如何获取程序计数器 (pc),以便在我中断程序时找出程序所在的点。
我尝试使用信号(SIGUSR1,Foo*)并调用回溯,但是我得到的是我在 raise(SIGUSR1) 时所在的线程的堆栈,而不是正在运行程序的线程。 我不确定这是否是正确的方法......
有什么建议吗?
【问题讨论】:
为什么不使用perf record
...这就是它的作用。 en.wikipedia.org/wiki/Perf_(Linux)
我需要自己实现它,而不是使用预先存在的工具。我正在研究创建工具
在 GDB 下我生成信号,然后执行 thread 1
以进入正确的线程,然后执行 bt
以获取堆栈。如果您想读取堆栈,可能有一个编程工具可以做到这一点。在过去,我做的很辛苦,检查返回指针,查看指令等。你仍然需要一个链接映射文件的等价物,这样你就可以将地址转换为函数名和行号。
我不确定你所说的“做线程 1”是什么意思。您的意思是有一种方法可以切换线程中间功能吗?我不知道存在这样的事情......如果我能做到,那么剩下的就不是问题了。
看看gstack
,它是一个包装脚本,它调用GDB来打印每个线程linux.die.net/man/1/gstack的回溯,也是这个SO问题***.com/questions/10833317/…
【参考方案1】:
如果您必须编写一个分析器,我建议您使用一个好的 (Zoom) 作为模型,而不是一个坏的 (gprof)。 这是它的特点。
有两个阶段。首先是数据收集阶段:
当它进行采样时,它会读取整个调用堆栈,而不仅仅是程序计数器。
即使进程由于 I/O、睡眠或其他原因而阻塞,它也可以进行采样。
您可以打开/关闭采样,以便仅在您关心的时间采样。例如,在等待用户输入内容时,进行采样是没有意义的。
其次是数据呈现阶段。 您拥有的是堆栈样本的集合,其中堆栈样本是内存地址的向量,几乎都是返回地址。 每个返回地址都表示函数中的一行代码,除非它在某些您没有符号信息的系统例程中。
有用信息的关键部分是居住分数(通常以百分比表示)。 如果总共有 m 个堆栈样本,并且代码行 L 出现在 n 个样本的任何位置,则其驻留分数为 n/m。 即使 L 在一个样本上出现的次数不止一次,这仍然是正确的,它仍然只是它出现在一个样本上。 驻留分数的重要性在于它直接表明 L 负责的时间分数。 如果您采集了 m=1000 个样本,并且 L 出现在其中 n=300 个样本上,则 L 的驻留率是 300/1000 或 30%。 这意味着如果 L 可以被移除,总时间将减少 30%。 它通常称为包含百分比。
您不仅可以确定代码行的驻留比例,还可以确定您可以描述的任何其他内容。例如,代码行 L 在某个函数 F 中。 因此,您可以确定函数的驻留比例,而不是代码行。 这将按功能为您提供包容性百分比。 您可以查看函数对,例如您看到函数 F 调用函数 G 的样本比例。 这将为您提供构成调用图的信息。
您可以从堆栈示例中获取各种信息。 常见的一种是“蝴蝶视图”,您可以将“焦点”放在一行 L 或函数 F 上,在堆栈样本的一侧显示其正上方的所有行或函数,在另一侧显示将其下方的所有功能线放在一边。 在每一个上,您都可以显示居住分数。 您可以在此处单击以尝试查找具有高驻留率的代码行,您可以找到消除或减少的方法。 这就是您加快代码速度的方式。
无论你为输出做什么,我认为让用户真正检查其中的一小部分,随机选择是非常重要的。 它们传达的洞察力远远超过任何浓缩信息的方法。
知道探查器应该做什么很重要,知道不做什么也很重要,即使很多其他探查器都在做这些事情:
自我时间。一个无用的号码。看看一些规模合理的程序,你就会明白为什么。
调用计数。查找具有高驻留率的代码无济于事,而且无论如何您都无法仅通过示例获得它。
高频采样。令人惊讶的是,有多少人(当然是分析器构建者)认为获取大量样本很重要。假设线 L 在 1000 个样本的 30% 上。那么它的真正包含百分比是 30 +/- 1.4%。另一方面,如果它在 10 个样本的 30% 上,则其包含百分比为 30 +/- 14%。 它仍然很大 - 大到可以修复。 大多数分析器发生的情况是人们认为他们需要“数值精度”,因此他们采集大量样本并积累他们所谓的“统计数据”,然后抛出带走样品。这就像挖掘钻石,称重,然后扔掉。真正的价值在于样本本身,因为它们会告诉您问题所在。
【讨论】:
“检查其中的一小部分,随机选择” - 这是最好的型号,售价为 1300 美元和 700 美元吗?为什么所有工具都给我每个函数的准确百分比,以及什么会给你的“随机选择”(如果它不能重现相同的结果)?我应该如何使用它来获取有关程序执行的低级详细信息,例如here?并非所有程序都有单一/少量热点,我确信您的随机分析器不是灵丹妙药。 @osgx:我的错误——我被打断了。与其用文字进行辩论,不如通过一个简短的视频here 引导您完成它。我说“如果你必须编写一个分析器”,因为我敢打赌,除了基于人工智能的分析器之外,没有任何可能的分析器可以为你提供与纯手动方法一样多的加速性能提升。 Mike Dunlavey,抱歉,该视频是关于您自己的随机分析器,而不是关于 Zoom。我已经有了我自己的非随机的……你们有类似的材料(视频或 pdf)用于 Zoom 的使用吗? PS:从你的视频大约 5:35。我的经验是有偏见的,但我可以从几个分析器那里获得未汇总的样本,而不仅仅是我自己的。 @osgx:Zoom 瞄准器上有演示。我不使用 Zoom - 我只提到它是因为,如果必须使用或构建一个分析器,它应该是 Zoom 之类的。我认为没有任何分析器会优于手动方法(就实现的加速而言)。我举了一个例子here,它的加速比是 730 倍,但还没有人用分析器实现这一点。如果您可以获得并使用未汇总的堆栈样本,那么这就是要走的路。我自己用 rprof 来做。【参考方案2】:您可以使用目标线程的pthread_kill
和tid (gettid()
) 向特定线程发送信号。
创建简单分析器的正确方法是使用setitimer
,它可以发送周期性信号(SIGALRM
或SIGPROF
),例如,每 10 毫秒;或 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有fork
和clone
的问题;它不适用于多线程应用程序。尝试创建辅助包装器: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
【讨论】:
非常感谢,我首先尝试 pthread 的事情,因为它接近我已经在做的事情,我正在使用 syscall() 获取 tid,因为我的编译器不支持 gettid,但它只是挂在 pthread_kill打电话... mattthehoopy,你现在有什么问题?我能提供什么帮助? 这篇文章帮助我解决了这个问题,最后一步是使用 pthread 函数获取我的主线程 ID。感谢所有帮助:) mattthehoopy,所以,如果您的问题得到充分回答,您可以通过单击答案投票按钮下方的“v”形按钮“接受”最佳答案。接受问题后视为关闭。如果您对创建分析器还有其他问题,请在此处添加评论并附上问题的链接。 PS:你使用了哪些 pthread 函数来获取线程 id? 酷,对不起,我的第一个线程。事实证明,您将 pthread_t 变量传递给 pthread_kill 函数。我在主线程中使用 pthread_self() 得到了这个变量:)以上是关于实现一个中断驱动的采样分析器的主要内容,如果未能解决你的问题,请参考以下文章
Android 事件分发事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 )