如何写一个探查器?

我想知道如何写一个探查器? 推荐哪些书籍和/或文章? 有人可以帮我吗?

有人已经做过这样的事吗?

我先看看那些开源项目:

然后我会看看JVMTI(而不是JVMPI)

鼓励很多,不是我们:)

如果您只是试图合理地了解该计划在大部分时间内花费的时间,那么Profilers并不会太难。 如果您对高准确性和最小干扰感到困扰,事情就会变得困难。

因此,如果yoyu只想要一个分析器给你的答案,请去找别人写的。 如果您正在寻找智力挑战,为什么不去写一个呢?

我已经写了几个,因为这些年来已经变得无关紧要的运行时环境。

有两种方法

  • 向每个函数或其他重要点添加内容以记录时间和位置。

  • 定时关闭定时器并查看程序当前所在的位置。

JVMPI版本似乎是第一种 – uzhin提供的链接表明它可以报告很多事情(参见1.3节)。 执行此操作会发生什么变化,因此分析可能会影响性能(如果您正在分析什么是非常轻量级但通常称为函数,它可能会误导)。

如果你可以得到一个定时器/中断告诉你程序计数器在中断时的位置,你可以使用符号表/调试信息来确定它当时所处的function。 这提供的信息较少,但破坏性较小。 通过遍历调用堆栈以识别呼叫者等可以获得更多信息。我不知道这些甚至可能在Java中…

保罗。

我写了一次,主要是为了让“深度采样”更加用户友好。 手动执行此方法时, 此处将对此进行说明 。 它是基于采样,而不是采取大量的小样本,你采取少量的大样本。

例如,它可以告诉你,指令I (通常是函数调用)花费你总执行时间的百分比X,或多或少,因为它在X%的样本上出现在堆栈上。

想一想,因为这是一个关键点 。 只要程序正在运行,调用堆栈就存在。 如果特定的调用指令I在X%的时间内处于堆栈中,那么如果该指令可能消失,则X%的时间将消失。 这不取决于I执行了多少次,或者函数调用需要多长时间。 因此,计时器和计数器都不为人知。 从某种意义上说,所有指令都是调用指令,即使它们只调用微码。

采样器的前提是,最好精确地知道指令I的地址( 因为这是你要找的 ),而不是精确地知道数字X%。 如果你知道你可以通过重新编码来节省大约30%的时间,那么你真的在乎你可能会减少5%吗? 你仍然想要解决它。 实际保存的时间量不会因你熟悉的X而变得更少或更多。

因此可以从定时器驱动样本,但坦率地说,我发现用户同时按下两个shift键来触发中断同样有用。 由于20个样本通常很多,这样您可以确保在相关时间(即不等待用户输入时)采样,这是非常充分的。 另一种方法是仅在用户按下两个shift键(或类似的东西)时执行定时器驱动的样本。

我并不关心采样可能会减慢程序的速度,因为目标不是测量速度,而是找到成本最高的指令。 修好后,整体加速很容易测量。

分析器提供的主要function是UI,因此您可以轻松地检查结果。 抽样阶段的结果是调用堆栈样本的集合,其中每个样本是指令的地址列表,其中每个指令但最后一个是调用指令。 用户界面主要是所谓的“蝴蝶视图”。 它具有当前的“焦点”,这是一种特殊的指令。 在左侧显示紧接该指令上方的调用指令,从堆栈样本中剔除。 如果焦点指令是一个调用指令,则它下方的指令显示在右侧,从样本中剔除。 在焦点指令上显示百分比,即包含该指令的堆栈百分比。 类似地,对于左侧或右侧的每个指令,百分比按每个这样的指令的频率进行细分。 当然,指令由文件,行号和它所在的函数名称表示。用户可以通过单击任何指令轻松浏览数据,使其成为新的焦点。

此UI的变体将蝴蝶视为二分,由函数调用指令的交替层和包含它们的函数组成。 这可以使每个function花费更多的时间。

也许它并不明显,所以值得一提的是这种技术的一些特性。

  • 递归不是问题,因为如果指令在任何给定的堆栈样本上出现不止一次,那么仍然只计算一个包含它的样本。 仍然可以确定,通过移除它所节省的估计时间是它所在的堆栈百分比。

  • 请注意,这与调用树不同。 无论调用树有多少不同的分支,它都会为您提供指令的成本。

  • UI的性能不是问题,因为样本的数量不需要非常大。 如果特定指令I是焦点,则很容易找到样本如何包含它,并且对于每个相邻指令,包含I的样本中还有多少包含其旁边的相邻指令。

  • 如前所述,采样速度不是问题,因为我们没有测量性能,我们正在诊断。 抽样不会对结果产生偏差,因为抽样不会影响整个计划的作用。 即使N指令完成任何次数,也需要N个指令完成的算法仍然需要N个指令。

  • 我经常被问到如何对以毫秒为单位完成的程序进行采样。 简单的答案是将它包装在外部循环中,以使其花费足够长的时间来进行采样。 您可以找出X%的时间,删除它,获得X%加速,然后删除外部循环。

我称之为YAPA(另一个性能分析器)的这个小小的分析器是基于DOS的,并做了一个很好的小演示,但是当我有认真的工作要做时,我会回到手动方法上。 这样做的主要原因是单独调用堆栈通常不足以告诉您为什么要花费特定周期的状态信息。 您可能还需要了解其他状态信息,以便更全面地了解当时程序正在执行的操作。 由于我发现手动方法非常令人满意,我搁置了工具。

在谈论分析时经常遗漏的一点是,您可以反复执行以找到多个问题。 例如,假设指令I1在5%的时间在堆栈上,并且I2在50%的时间在堆栈上。 20个样本很容易找到I2 ,但也许不是I1 。 所以你修复了I2 。 然后你再做一遍,但现在I1需要10%的时间,所以20个样本可能会看到它。 这种放大效果允许重复应用轮廓以实现大的复合加速因子。

JVMPI规范: http : //java.sun.com/j2se/1.5.0/docs/guide/jvmpi/jvmpi.html

我向你的勇气和勇气致敬

编辑:正如用户Boune所说,JVMTI: http : //java.sun.com/developer/technicalArticles/Programming/jvmti/

作为另一个答案,我只是在sourceforge上看了LukeStackwalker。 它是一个很好的,很小的堆栈采样器示例,如果你想编写一个探查器,这是一个很好的起点。

在我看来,这是正确的做法:

  • 它对整个调用堆栈进行采样。

叹息……到目前为止还很近。 在这里,IMO就是它(以及像xPerf这样的其他堆栈采样器)应该做的事情:

  • 它应该保留原始堆栈样本。 实际上,它在样本中总结了function级别。 这丢失了定位有问题的呼叫站点的关键行号信息。

  • 它不需要采取如此多的样本,如果存储它们是一个问题。 由于典型的性能问题成本从10%到90%,因此20-40个样品将非常可靠地显示出来。 数百个样本提供更高的测量精度,但它们不会增加定位问题的可能性。

  • UI应该根据语句而不是函数进行总结。 如果保留原始样品,这很容易做到。 附加到声明的关键措施是包含它的样本的分数。 例如:

    5/20 MyFile.cpp:326 for(i = 0; i

这表示在调用strlen的过程中,MyFile.cpp中的第326行出现在20个样本中的5个中。 这非常重要,因为您可以立即看到问题,并且您知道修复它可以获得多少加速。 如果用s[i]替换strlen(s) ,它将不再花费时间在该调用中,因此这些样本不会发生,并且加速将大约为1 /(1-5 / 20)= 20 /( 20-5)= 4/3 = 33%加速。 (感谢David Thornley提供此示例代码。)

  • 用户界面应该有一个显示语句的“蝴蝶”视图。 (如果它也显示了函数,那没关系,但语句才是真正重要的。)例如:

    3/20 MyFile.cpp:502 MyFunction(myArgs)
    2/20 HisFile.cpp:113 MyFunction(hisArgs)

    5/20 MyFile.cpp:326 for(i = 0; i

    5/20 strlen.asm:23 …一些汇编代码……

在此示例中,包含for语句的行是“关注焦点”。 它发生在5个样本上。 它上面的两行表示,在其中3个样本中,它是从MyFile.cpp:502调用的,在其中2个样本中,它是从HisFile.cpp:113调用的。 它下面的一行表示,在所有5个样本中,它都是strlen (毫不奇怪)。 通常,焦点线将具有“父母”树和“儿童”树。 如果由于某种原因,焦点线不是你可以修复的东西,你可以上升或下降。 目标是找到尽可能多的样本可以修复的行。

重要提示:不应将分析视为您曾经做过的事情。 例如,在上面的示例中,我们通过修复一行代码获得了4/3的加速。 当重复该过程时,其他有问题的代码行应该以他们之前执行的频率的4/3显示,因此更容易找到。 我从来没有听说过有人在讨论迭代分析过程,但是获得整体大型复合加速是至关重要的。

PS如果在单个样本中多次出现语句,则表示发生了递归。 这不成问题。 它仍然只计为包含该语句的一个样本。 仍然是声明的成本由包含它的样本的分数近似的情况。