探查器如何对正在运行的程序进行采样?

Posted

技术标签:

【中文标题】探查器如何对正在运行的程序进行采样?【英文标题】:How does a profiler sample a running programe? 【发布时间】:2018-03-08 20:40:28 【问题描述】:

我对此只有一些粗略的想法,所以我想有一些更实际的想法。欢迎对 Linux、Unix 和 Windows 提出想法。

我脑海中的粗略想法是:

分析器在目标进程中设置某种类型的定时器和定时器中断处理程序。当它的处理程序获得控制权时,它读取并保存指令指针寄存器的值。采样完成后,它会统计每个 IP 寄存器值的出现次数,然后我们可以知道所有采样的程序地址中的“top hitters”。

但我实际上不知道该怎么做。有人可以给我一些基本但实用的想法吗?例如,总是使用什么样的计时器(或等效的)?如何读取 IP reg 值?等等(我认为当执行进入分析器的处理程序时,IP应该指向处理程序的入口,而不是目标程序中的某个地方,所以我们不能简单地读取当前的IP值)

感谢您的回答!


感谢 Peter Cordes 和 Mike Dunlavey 的回答。

彼得的回答告诉了如何读取其他进程的寄存器和内存。现在我意识到分析器不必在目标进程的“内部”执行,相反,它只是使用 ptrace(2) 从外部读取目标的 reg/mem。它甚至不必暂停目标,因为 ptrace 无论如何都会这样做。

Mike 的回答表明,对于性能分析,计算堆栈跟踪的出现次数比计算 IP 寄存器值更有意义,因为在采样时在系统模块中执行时后者可能会提供过多的噪声信息.

非常感谢你们!

【问题讨论】:

相关:How does gdb read the register values of a program / process it's debugging? How are registers associated with a process?。因此,您可以将 Mike Dunlavey 最喜欢的分析技术(stopping with a debugger)自动化几次,这基本上就是您的想象。 一些分析器在进程本身中构建了额外的检测(如gcc -pg),以记录每个函数进入时的时间和调用堆栈。但是性能计数器事件是不同的。 Linux 有一个与ptrace 分开的perf API,用于分析其他进程。有关一些示例,请参阅brendangregg.com/perf.html。 IDK 正是 Windows 上的 VTune 使用的;我认为它有自己的内核模块/驱动程序,可以访问硬件性能计数器并收集硬件性能计数器产生的中断(创建一个示例)。 【参考方案1】:

很高兴你想这样做。建议 - don't try to mimic gprof.

您需要做的是随机或伪随机时间对调用堆栈进行采样,而不仅仅是 IP。

第一个原因 - I/O 和系统调用可能深深地隐藏在应用程序中,并且在大部分时间中花费大量时间,在此期间 IP 无意义但堆栈有意义。 (“CPU profilers”只是闭上眼睛。)

第二个原因 - 查看 IP 就像试图通过查看马尾上的毛发来了解一匹马。要分析程序的性能,您需要知道为什么花费时间,而不仅仅是它。堆栈告诉为什么

gprof 的另一个问题是它让人们认为您需要大量样本——越多越好——统计精度。 但这假设您正在大海捞针中寻找针,将其移除几乎不会节省任何费用 - 换句话说,您假设(attaboy/girl 程序员)那里没有任何,就像下面的牛干草。 好吧,我从来没有见过没有牛在干草丛中的软件,而且不需要很多样本就可以找到它们。

如何获取样本:有一个定时器中断和读取堆栈(二进制)只是一个技术问题。很久以前我就想出了怎么做。你也可以。每个调试器都会这样做。但是要将其转换为代码名称和位置需要映射文件或类似文件,这通常意味着调试构建(未优化)。你可以从优化后的代码中获取地图文件,但是优化器已经把代码打乱了,所以很难理解。

是否值得在未优化的代码中取样?我想是的,因为有两种加速,一种是编译器可以做的,一种是你可以做但编译器做不到的。后者是奶牛。 所以我和许多其他程序员首先要做的是使用随机采样对未优化代码进行性能调整。当所有的牛都出来后,打开优化器,让编译器发挥它的魔力。

【讨论】:

非常感谢您提供的信息丰富的回答。相信以后还要多读几遍,想办法捕捉定时器中断。我完全同意计算特定堆栈跟踪的出现更有意义并且产生更少的噪音。但是通过检查 IP 和堆栈跟踪,我们可以准确地找出用户源代码中的哪个语句花费了大部分 CPU 时间。我想通过调试信息,我们可以将用户源代码中的语句或函数调用与系统模块中的语句或函数调用区分开来,但这只是一个猜测。再次感谢您。 @dʒəu:IP 包含在堆栈跟踪中。你说“我们可以准确地找出用户源代码中的哪个语句花费了大部分 CPU 时间”。如果用户源代码中花费最多时间的语句是函数调用,如字符串比较,new,移动UI控件,print 或其他任何内容,IP 将不存在,但该行代码位于堆栈示例中,您可以在其中看到它。如果该行花费 90% 的时间,则 90% 的时间它都在堆栈上,您可以在其中看到它。 这就是重点。 我不得不反对分析未优化的代码。当您可以使用gcc -march=native -O3 -g -fno-omit-frame-pointer 构建以获得带有调试符号和帧指针的优化代码时,这似乎毫无意义,因此您可以使用简单的基于帧指针的堆栈展开而不是复杂的.eh_frame 东西。如果您分析未优化的构建,尤其是 C++ 的构建,一旦找到热点并开始处理它,您将浪费时间优化 -O0。 That is pointless. @PeterCordes:这是关于在优化构建中获取调试信息的好信息。谢谢。你专注于一个真实的“热点”,这有时会发生。我正在谈论的(非常常见的)类型是堆栈的一半(优化没有实现) 17 个打印语句中的 1 个,或 new 调用,或获取国际化字符串的系统调用等,位于 9 /10 个堆栈样本,因此它花费了大约 90% 的时间,但是您无法分辨它是哪一个,因为优化器对代码进行了混合主控。没有有意义的行号。干杯。

以上是关于探查器如何对正在运行的程序进行采样?的主要内容,如果未能解决你的问题,请参考以下文章

探查器是不是会错过花在被阻止呼叫上的时间?

如何从 .NET 应用程序收集探查器数据

JvisualVM 中的采样器和探查器有啥区别?

使用 ETW 对 .NET 应用程序进行性能诊断

如何知道 Python 编程是不是在探查器下运行?

探查器可以更改递归调用在 Java 中运行的时间吗?