【问题标题】:Why is this method a hot spot?为什么这种方法会成为热点?
【发布时间】:2013-04-10 13:08:35
【问题描述】:

我正在编写一个(简单!)线性代数库。在matrix multiplication 的实现中,VisualVM 性能样本告诉我,当乘以大型矩阵 (5k x 120k) 时,该算法将 85% 的时间(特别是“自我时间”)花在以下方法中:

public double next() {
    double result;

    if(hasNext())
        result = vis[i++].next();
    else
        throw new IllegalStateException("No next value");

    return result;
}

无需过多介绍(抱歉,我无法分享更多代码),此方法是矩阵“迭代器”的next() 方法。 (你可以把这个方法所在的类想象成一个由单个列迭代器组成的行迭代器,这些列迭代器存储在vis 中。)我并不感到惊讶,因为它是一个迭代器,所以这个方法被调用了很多次,但是我am 令程序花费大量时间 这个方法感到惊讶。这个方法做的不多,为什么要花时间在这里呢?

以下是我要问的具体问题:

  1. 我遇到了一些 VisualVM 的“陷阱”吗?例如,JIT 是否会以某种方式混淆 VisualVM,从而导致 VisualVM 将时间归因于错误的方法?
  2. 为什么程序会花时间在这里?该方法只是没有做太多。特别是,我认为缓存效应不能解释这个问题,因为vis 数组比被相乘的矩阵的数据要小得多。

如果有用的话,这里是我上面贴的方法的jad反汇编:

public double next()
{
    double result;
    if(hasNext())
//*   0    0:aload_0         
//*   1    1:invokevirtual   #88  <Method boolean hasNext()>
//*   2    4:ifeq            32
        result = vis[i++].next();
//    3    7:aload_0         
//    4    8:getfield        #42  <Field VectorIterator[] vis>
//    5   11:aload_0         
//    6   12:dup             
//    7   13:getfield        #28  <Field int i>
//    8   16:dup_x1          
//    9   17:iconst_1        
//   10   18:iadd            
//   11   19:putfield        #28  <Field int i>
//   12   22:aaload          
//   13   23:invokeinterface #72  <Method double VectorIterator.next()>
//   14   28:dstore_1        
    else
//*  15   29:goto            42
        throw new IllegalStateException("No next value");
//   16   32:new             #89  <Class IllegalStateException>
//   17   35:dup             
//   18   36:ldc1            #91  <String "No next value">
//   19   38:invokespecial   #93  <Method void IllegalStateException(String)>
//   20   41:athrow          
    return result;
//   21   42:dload_1         
//   22   43:dreturn         
}

提前感谢你们的帮助!

【问题讨论】:

  • 我猜这真的取决于 hasNext() 和 next() 调用,因为我们不知道底层对象我不能假设它们是 O(1) 并且该方法应该非常快,即据我们所知,hasNext() 可能会绕太阳转 1000 倍
  • @RuntimeError, "self time" should 只包括在方法本身内花费的时间,所以 hasNext() 和 sub-next() 调用 should 与归因于此方法的自身时间无关。 (如果您有不同的认识,请纠正我!)
  • next 是否在调用自身(递归)?如果递归很深,这将是一个很好的理由......
  • @assylias 不,它没有。此next() 方法调用的next() 方法位于不同的类中。不过,这点很好。
  • @sigpwned 你的方法很短,所以它可能会在某个阶段被内联,which could confuse the profiler

标签: java performance optimization visualvm


【解决方案1】:

我发现这个方法看起来像一个热点,因为 VisualVM 被指示在其分析中忽略来自 JRE 的方法。在那些“被忽略”的方法上花费的时间(表面上)被滚动到调用堆栈的最顶部非忽略条目的自身时间中。

下面是 VisualVM 中的设置屏幕,包括导致数据错误的“不分析包”设置。要调整“忽略类”设置,您必须 (1) 点击以红色突出显示的“设置”复选框,然后 (2) 调整以蓝色突出显示的类设置。

根据您正在做的事情,至少不要忽略 java.*javax.* 包可能是有意义的。

【讨论】:

    【解决方案2】:

    忘记分析器。只需暂停这该死的东西几次并检查堆栈。如果 85% 的时间进入该例行程序,那么在每次暂停时,您将有 85% 的机会准确地看到它在该例程中的位置,以及它的确切来源。 您甚至可以看到它在矩阵相乘过程中的位置。 成千上万的样本不会告诉你这一点。

    我自己的感觉是,调用该函数,然后执行 hasNext,然后对每个元素执行 Next 会比 i++ 慢很多。

    【讨论】:

      【解决方案3】:

      我从经验中不了解 VisualVM。

      首先确定它是否检测字节码以收集统计信息。如果是这样,请不要再犹豫了 - 检测一个简短的方法总是会过度膨胀其自身时间(测量时间和增加统计计数器比方法本身花费更多的时间)。

      但是迭代器总是可能比计算本身消耗更多的时间。想象一下,只是总结一个矩阵。与调用方法、检查不变量并最终访问数组相比,将浮点值添加到局部 sum 变量所需的时间要少得多。

      【讨论】:

      • 我刚刚查看了VisualVM的文档。它是一个检测分析器。
      • 但是,我相信我在“采样”模式下使用它,这意味着它应该定期“冻结”CPU(可能每秒数十或数百次),然后看看会发生什么当时正在该进程中的每个线程上运行。这是一种相当不显眼的方法(根据经验),不应随意夸大在该方法中花费的时间,IMO。
      • 好吧,我对 VisualVM 不熟悉,但是文档说“在分析应用程序性能时,VisualVM 检测分析应用程序的所有方法”在 CPU 分析 (@ 987654321@)。不确定这是您在做什么,还是您使用其他选项?
      • 我使用的“采样器”选项与“分析器”选项不同,这就是为什么我认为它是采样的原因。不幸的是,在线指南似乎没有涵盖它。可以看到选项here的截图供参考。
      • 采样不使用仪器。
      猜你喜欢
      • 2014-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-15
      • 1970-01-01
      • 2016-02-15
      相关资源
      最近更新 更多