为什么Java CPU配置文件(使用visualvm)在一个什么都不做的方法上显示如此多的命中?

时间:2014-05-24 22:53:15

标签: java performance visualvm

这是我以前在其他环境中使用其他分析工具时所看到的,但在这种情况下它特别引人注目。

我正在获取一个运行约12分钟的任务的CPU配置文件,并且它显示了几乎一半的时间花费在一个字面上无所作为的方法:它已经空了身体。是什么导致这个?我不相信这种方法被称为荒谬的次数,当然不会占到执行时间的一半。

对于它的价值,所讨论的方法称为startContent(),它用于通知解析事件。事件传递给一系列过滤器(可能是十几个),每个过滤器上的startContent()方法除了在链中的下一个过滤器上调用startContent()外几乎没有任何作用。

这是纯Java代码,我在Mac上运行它。

附件是CPU采样器输出的屏幕截图:

CPU sampler screenshot

以下是显示调用堆栈的示例:

CPU sampler sample call stack

(由于休假而延迟)以下是一些显示探查器输出的图片。这些数字远远超出我期望的配置文件的样子。探查器输出似乎完全有意义,而采样器输出是虚假的。

CPU profiler output 1 CPU profiler output 2

正如你们中的一些人已经猜到的那样,有问题的工作是运行Saxon XML模式验证器(在9Gb输入文件上)。该配置文件显示大约一半的时间用于验证元素内容与简单类型(在endElement处理期间发生),大约一半用于测试关键约束的唯一性;两个分析器视图显示了该任务的这两个方面所涉及的活动。

我无法提供来自客户端的数据。

4 个答案:

答案 0 :(得分:7)

我没有使用VisualVM,但我怀疑问题可能是因为这种空方法的检测开销。 Here's the relevant passage in JProfiler's documentation (which I have used extensively):

  

如果方法调用记录类型设置为动态检测,则会检测所有已分析类的方法。这会产生一些开销,这对于执行时间非常短的方法很重要。如果非常频繁地调用这些方法,那么这些方法的测量时间将会很高。此外,由于仪器,可能会阻止热点编译器优化它们。在极端情况下,这种方法成为主要的热点,尽管对于未经检测的运行不是这样。一个例子是读取下一个字符的XML解析器的方法。此方法返回非常快,但可以在短时间内调用数百万次。

基本上,分析器会添加它自己的&#34;时间长度检测代码&#34;,基本上,但是在一个空方法中,分析器将花费所有时间来做那个< / em>而不是实际允许该方法运行。

如果可能的话,我建议告诉VisualVM停止检测该线程,如果它支持这样的过滤。

答案 1 :(得分:2)

通常假设使用分析器比发现性能问题要好得多(而不是测量事物),而不是其他任何东西,实际上 - 肯定比随机暂停的骨头简单方式更好。

这种假设只是普遍的智慧 - 它没有理论或实践的基础。 有许多关于剖析的学术同行评审论文,但我读过的都没有解决这一点,更不用说证实它了。 它是学术界的一个盲点,不是一个大盲点,但它就在那里。

现在回答你的问题 -

在显示调用堆栈的屏幕截图中,这就是所谓的&#34; hot path&#34;,约占线程内CPU时间的60%。假设代码与&#34; saxon&#34;这个名字就是你感兴趣的,就是这样:

net.sf.saxon.event.ReceivingContentHandler.startElement
net.sf.saxon.event.ProxyReceiver.startContent
net.sf.saxon.event.ProxyReceiver.startContent
net.sf.saxon.event.StartTagBuffer.startContent
net.sf.saxon.event.ProxyReceiver.startContent
com.saxonica.ee.validate.ValidationStack.startContent
com.saxonica.ee.validate.AttributeValidator.startContent
net.sf.saxon.event.TeeOutputter.startContent
net.sf.saxon.event.ProxyReceiver.startContent
net.sf.saxon.event.ProxyReceiver.startContent
net.sf.saxon.event.Sink.startContent

首先,我认为它必须做I / O,或者至少等待其他一些过程给它内容。如果是这样,你应该看看挂钟时间,而不是CPU时间。

其次,问题可能出现在一个函数调用下面的函数的任何调用站点。如果任何此类呼叫不是真正必要的并且可以不经常跳过或完成,则会减少很长一段时间。 我怀疑StartTagBuffervalidate,但你最了解。

我还可以提出其他一些观点,但这些都是主要观点。

在您修改问题后添加。 我倾向于假设您正在寻找优化代码的方法,而不仅仅是为了获取数字的方法。

它看起来仍然只是CPU时间,而不是挂钟时间,因为热路径中没有I / O.也许你的情况还可以,但是这意味着你的12分钟挂钟时间,I / O等待可能需要11分钟,CPU需要1分钟。如果是这样,你可以在CPU部件中减少30秒的脂肪,并且只将时间缩短30秒。 这就是为什么我更喜欢对挂钟时间进行采样的原因,所以我有全面的观点。

单凭热门路径,您无法获得真实的情况。 例如,如果热路径表示功能F在热路径上,例如40%的时间,则仅表示F成本不低于超过40%。它可能更多,因为它可能在其他不太热的路径上。所以你可以有很多机会来加快速度,但它并没有在探查者选择向你展示的特定路径中获得太多曝光,所以你不会给予很多关注。 事实上,一个重要的时间可能不会出现 ,因为在任何特定的热门道路上总会有更大的东西,比如new,或者因为它走了通过多个名称,例如模板化的集合类构造函数。

它没有向您显示任何行分辨率信息。 如果您想因成本原因检查所谓的高成本例程,则必须查看其中的行。在查看例行公事时,有一种倾向:“它只是在做他们应该做的事情。”#34;但如果你正在寻找一个特定的昂贵的代码,通常是方法调用,您可以问&#34;是否真的有必要进行此调用?也许我已经掌握了这些信息。&#34;它更具体地建议你可以修复什么。

它能否实际显示一些原始堆栈样本? 根据我的经验,这些信息比任何摘要信息更丰富,比如分析器可以呈现的热门路径。 要做的是检查样本并充分了解程序正在做什么,以及在那个时间点的原因。 然后重复几个样本。 您将看到不需要完成的事情,您可以修复以获得实质性的加速。 (除非代码已经是最优的,在这种情况下,知道它会很好。) 关键是,你正在寻找问题,而不是测量。 Statistically, it's very rough, but good enough,没有问题会逃脱。

答案 2 :(得分:1)

我的猜测是Sink.startContent实际 的方法被称为荒谬的次数。

您的屏幕截图显示了Sampling标签,如果用户处于较长的时间间隔内,通常会产生实际的时间安排。如果您改为使用Profiler标签,则还会获得调用计数。 (你也会得到不那么逼真的时间,你的程序会变得非常慢,但你只需要这样做几秒钟就可以了解调用计数)。

很难预测HotSpot会执行哪些优化,特别是内联,并且采样分析器只能将内联方法的时间归因于呼叫站点。我怀疑撒克逊人中的一些调用代码可能由于某种原因归结为您的空回调函数。在这种情况下,您只需要承担XML的成本,并且切换到不同的解析器可能是唯一的选择。

答案 3 :(得分:0)

我已经从这个帖子中获得了很多有用的信息和指导,非常感谢。但是,我不认为核心问题已经得到解答:为什么VisualVM中的CPU采样在一个什么都不做的方法中给出了非常多的命中率,而且这个问题并不比许多人更频繁地调用其他方法?

对于未来的调查,我将依赖于探查器而不是采样器,现在我已经了解了它们的不同之处。

从简介中我还没有真正获得关于这项具体任务的大量新信息,因为它已经在很大程度上证实了我猜想的内容;但这本身很有用。它告诉我,加速这个特定的过程没有灵丹妙药,但已经对一些严肃的重新设计可能实现的目标设置了限制,例如,可能的未来增强似乎有一些承诺是生成字节码验证器对于架构中的每个用户定义的简单类型。