16

我想知道如何编写分析器?推荐什么书和/或文章?任何人都可以帮助我吗?

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

4

5 回答 5

13

令人鼓舞的很多,不是我们:)

如果您只是想对程序大部分时间花在哪里有一个合理的了解,那么分析器并不是太难。如果您对高精度和最小干扰感到困扰,事情就会变得困难。

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

我已经写了几个,用于多年来变得无关紧要的运行时环境。

有两种方法

  • 在记录时间和位置的每个功能或其他重要点上添加一些内容。

  • 让计时器定期启动并查看程序当前所在的位置。

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

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

保罗。

于 2008-12-15T16:45:21.670 回答
8

我会先看看那些开源项目:

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

于 2008-12-16T00:20:04.093 回答
8

我曾经写过一篇,主要是为了让“深度采样”更加用户友好。当您手动执行该方法时,将在此处进行说明。它是基于抽样的,但不是取大量的小样本,而是取少量的大样本。

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

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

采样器基于这样一个前提,即I准确地知道指令的地址(因为那是您要查找的)比准确地知道数字 X% 更好。如果你知道通过重新编码可以节省大约 30% 的时间,你真的关心你可能会节省 5% 的时间吗?你仍然会想要修复它。它实际节省的时间不会因您准确了解 X 而减少或增加。

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

我并不担心采样可能会减慢程序,因为目标不是测量速度,而是找到最昂贵的指令。修复某些东西后,整体加速很容易衡量。

分析器提供的主要内容是 UI,因此您可以轻松地检查结果。采样阶段产生的是调用堆栈样本的集合,其中每个样本是指令地址列表,其中除最后一条指令之外的每条指令都是调用指令。UI 主要是所谓的“蝴蝶视图”。它有一个当前的“焦点”,这是一个特定的指令。左侧显示了该指令正上方的调用指令,这些指令是从堆栈样本中挑选出来的。如果焦点指令是一个调用指令,那么它下面的指令会出现在右侧,就像从样本中挑选出来的一样。在焦点指令上显示一个百分比,即包含该指令的堆栈的百分比。同样对于左边或右边的每条指令,该百分比按每条此类指令的频率细分。当然,指令由文件、行号和它所在的函数的名称表示。用户可以通过单击任何指令轻松浏览数据,使其成为新的焦点。

这个 UI 的一个变体将蝴蝶视为两部分,由交替的函数调用指令层和包含它们的函数组成。这可以让每个函数花费的时间更加清晰。

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

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

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

  • UI 的性能不是问题,因为样本的数量不需要很大。如果一个特定的指令 I 是焦点,那么很容易找到样本包含它的方式,以及对于每条相邻指令,有多少包含 I 的样本还包含它旁边的相邻指令。

  • 如前所述,采样速度不是问题,因为我们不是在测量性能,而是在诊断。抽样不会影响结果,因为抽样不会影响整个程序的功能。一个算法需要 N 条指令才能完成,即使它被暂停了任意次数,它仍然需要 N 条指令。

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

这个我称之为 YAPA(又一个性能分析器)的小分析器是基于 DOS 的,并且做了一个不错的小演示,但是当我有严肃的工作要做时,我会求助于手动方法。主要原因是调用堆栈本身通常不足以告诉您为什么要花费特定周期。您可能还需要了解其他状态信息,以便更全面地了解程序当时在做什么。由于我发现手动方法非常令人满意,因此我搁置了该工具。

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

于 2009-04-10T19:36:13.267 回答
2

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

我向你的勇气和勇敢致敬

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

于 2008-12-15T16:15:15.913 回答
0

作为另一个答案,我只是在 sourceforge 上查看了 LukeStackwalker。这是一个不错的小型堆栈采样器示例,如果您想编写分析器,它也是一个不错的起点。

在我看来,这就是它的正确之处:

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

叹息……那么近却那么远。在这里,IMO 是它(以及其他堆栈采样器,如 xPerf)应该做的:

  • 它应该保留原始堆栈样本。事实上,它在采样时在功能级别进行汇总。这会丢失定位有问题的呼叫站点的关键线路号码信息。

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

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

    5/20 MyFile.cpp:326 for (i = 0; i < strlen(s); ++i)

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

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

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

    5/20 MyFile.cpp:326 for (i = 0; i < strlen(s); ++i)

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

在此示例中,包含for语句的行是“关注焦点”。它发生在 5 个样品上。它上面的两行表示,在其中 3 个样本上,它是从 调用的MyFile.cpp:502,而在其中 2 个样本上,它是从 调用的HisFile.cpp:113。它下面的行表示在所有 5 个样本中,它都在strlen(这并不奇怪)。一般来说,焦点线会有一棵“父母”树和一棵“孩子”树。如果由于某种原因,焦点线不是您可以修复的,您可以向上或向下。目标是在尽可能多的样本上找到可以修复的行。

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

PS 如果一个语句在一个样本中出现不止一次,这意味着正在发生递归。这不成问题。它仍然只算作一个包含该语句的样本。语句的成本仍然是由包含它的样本的分数来近似的。

于 2009-05-17T13:33:00.047 回答