Java Profiling:私有属性 Getter 具有较大的基准时间

Posted

技术标签:

【中文标题】Java Profiling:私有属性 Getter 具有较大的基准时间【英文标题】:Java Profiling: Private Property Getter has Large Base Time 【发布时间】:2009-03-27 16:00:23 【问题描述】:

我正在使用 TPTP 来分析一些运行缓慢的 Java 代码,我遇到了一些有趣的事情。我的一个私有属性 getter 在执行时间分析结果中具有很大的基准时间值。公平地说,这个属性被调用了很多次,但我从来没有想到这样的属性会花费很长时间:

public class MyClass
    private int m_myValue;    
    public int GetMyValue()
        return m_myValue;
    

好的,显然类中还有更多内容,但是正如您所见,当调用 getter 时没有其他任何事情发生(只返回一个 int)。给你一些数字:

大约 30% 的运行调用是 在吸气剂上(我正在努力减少 这个) 大约 25% 的基准时间 运行在这个 getter 中花费 平均基准时间为 0.000175s

为了比较,我在不同的类中有另一个方法使用这个 getter:

private boolean FasterMethod(MyClass instance, int value)
    return instance.GetMyValue() > m_localInt - value;

平均基准时间要低得多,为 0.000018 秒(低一个数量级)。

这里有什么问题?我认为有些东西我不明白或我遗漏了一些东西:

    返回本地原语是否真的比返回计算值花费更长的时间? 我应该查看基准时间以外的指标吗? 这些结果是否具有误导性,我需要考虑使用其他分析工具吗?

编辑1:根据下面的一些建议,我将该方法标记为最终方法并重新运行测试,但得到了相同的结果。

编辑 2:我安装了 YourKit 的演示版来重新运行我的性能测试,YourKit 结果看起来更接近我的预期。我将继续测试 YourKit 并报告我的发现。

编辑 3: 更改为 YourKit 似乎解决了我的问题。我能够使用 YourKit 来确定我的代码中的实际慢点。下面有一些优秀的 cmets 和帖子(适当地赞成),但我接受第一个建议 YourKit 为“正确”的人。 (我与 YourKit 没有任何关联/YMMV)

【问题讨论】:

听起来有误导性 - 我会尝试第二个分析器来看看... 不是很相关,但是Java编码标准是方法名以小写开头,不要在变量前加“m_”。 您也可以尝试使用 YourKit 进行分析,这需要付费,但有 14 天的免费试用期。 如果你能用 Netbeans 来做,试一试,它是免费的(而且分析器很好,YourKit 也是,我都用过)。 【参考方案1】:

如果可能,请尝试使用另一个分析器(Netbeans 可以很好地工作)。根据您的代码设置方式,这可能很难做到。

就像许多其他工具一样,不同的分析器可能会产生不同的信息。

返回一个本地原语真的比返回一个原语花费更长的时间吗? 计算值?

返回实例变量比返回局部变量(取决于 VM)花费的时间更长。您拥有的 getter 很简单,因此它应该被内联,因此它变得与访问公共实例变量一样快(同样,它比访问局部变量慢)。

但您没有本地值(方法中的本地含义,而不是类中的本地含义)。

“本地”到底是什么意思?

我应该查看基准时间以外的指标吗?

我没有使用过 Eclipse 工具,所以我不确定它是如何工作的……如果它是一个跟踪或采样分析器,它可能会有所不同(对于这样的事情,两者可以给出不同的结果)。

这些结果是否具有误导性,我需要考虑其他一些 分析工具?

我会考虑另一种工具,只是看看结果是否相同。

基于 cmets 编辑:

如果它是一个采样分析器,那么本质上,每个“n 时间单位”都会对程序进行采样以查看程序的位置。如果您调用一种方法的次数多于另一种方法,它将显示为在被调用次数较多的方法中(很可能该方法正在运行)。

跟踪分析器将代码添加到您的程序(称为检测的过程)以从本质上记录正在发生的事情。

跟踪分析器速度较慢但更准确,它们还需要更改程序(检测过程),这可能会引入错误(不是我听说过它发生...但我相信它至少在他们正在开发探查器)。

采样分析器更快但不太准确(它们只是猜测一行代码的执行频率)。

因此,如果 Eclipse 使用采样分析器,您可能会看到您认为奇怪的行为。更改为跟踪分析器将显示更准确的结果。

如果 Eclipse 使用跟踪分析器,那么更改分析器应该会显示相同的结果(但是新的分析器可能会让您更清楚地了解正在发生的事情)。

【讨论】:

你是对的:我打错了。我应该说“实例”变量,而不是“本地”变量。我的部分困惑来自 FasterMethod() 方法。我同时使用本地(传入)和实例变量,但它比使用实例变量更快(根据 TPTP)。 四处挖掘我认为 Eclipse 使用了采样分析器。听起来它也可以通过插件或选项(对不起,不是 Eclipse 用户)来执行跟踪。【参考方案2】:

听起来确实有点误导 - 也许分析器正在删除一些优化?

只是为了好玩,尝试将方法设为 final,这将更容易内联。这很可能是属性和 FasterMethod 之间的区别。在实际使用中,HotSpot 甚至会内联虚拟方法,直到它们第一次被覆盖 (IIRC)。

编辑:回应 Brian 的评论:是的,通常的情况是,制作最终的东西不会提高性能(尽管它在设计方面可能是一件好事 :) 因为 Hotspot 通常会根据它是否被覆盖来确定它是否可以内联。我的意思是这个分析器可能搞砸了。

编辑:我现在已经设法重现 HotSpot “撤消”尚未扩展的类(或尚未覆盖的方法)的优化的方式。对于服务器虚拟机来说,这比客户端更难做到,但我已经做到了:)

public class Test

    public static void main(String[] args)
        throws Exception
    
        final long iterations = 1000000000L;
        Base b = new Base();
        // Warm up Hotspot
        time(b, 1000);

        // Before we load Derived
        time(b, iterations);

        // Load Derived and use it quickly
        // (Just loading is enough to make the client VM
        // undo its optimizations; the server VM needs more effort)
        Base d = (Base) Class.forName("Derived").newInstance();
        time(d, 1);

        // Time it again with Base
        time(b, iterations);
    

    private static void time(Base b, long iterations)
    
        long total = 0;
        long start = System.currentTimeMillis();
        for (long i = 0; i < iterations; i++)
        
            total += b.getValue();
        
        long end = System.currentTimeMillis();
        System.out.println("Time: " + (end-start));
        System.out.println("Total: " + total);
    


class Base

    public int getValue()  return 1; 


class Derived extends Base

    @Override
    public int getValue()  return 2; 

【讨论】:

如果 final 有所作为我会很感兴趣。理论上,JVM 可以安全地缓存该值,而不必读取(慢速)主内存。 在此处查看 Brian Goetz 的信息。最终确定 - ibm.com/developerworks/java/library/j-jtp04223.html 仅供参考 - 我将该方法标记为最终方法并重新运行测试,但得到了相同的结果。 对。呃,好吧。我正在编辑我的答案,并举了一个无论如何都会产生影响的例子,只是因为它很有趣:) 是的,Hotspot 可以内联事物,然后如果加载的类破坏了内联,则撤消内联。 C++ 不能(轻松地)内联虚拟方法(有些编译器显然可以做到这一点,但会占用运行时使用量)。未来 Java 的一个很好的例子是缓慢的辩论:-)【参考方案3】:

这听起来很奇怪。您不会错误地调用了覆盖方法,是吗?

我很想下载a demo version of YourKit。设置起来很简单,它应该可以指示真正发生的事情。如果 TPTP 和 YourKit 都同意,那么就会发生一些奇怪的事情(我知道这没有多大帮助!)

【讨论】:

好问题,但没有覆盖方法。【参考方案4】:

曾经对这类方法的性能产生很大影响的东西(尽管这可能在某种程度上是历史性的)是调用方法的大小可能是一个问题。 HotSpot(和严肃的竞争对手)很乐意内联小方法(有些可能会在同步/最终尝试时窒息)。但是,如果调用方法很大,则可能不会。这对于旧版本的 HotSpot C1/客户端来说尤其成问题,它的寄存器分配算法非常糟糕(现在它的算法既非常好又快)。

【讨论】:

以上是关于Java Profiling:私有属性 Getter 具有较大的基准时间的主要内容,如果未能解决你的问题,请参考以下文章

java里一个私有的属性如何在其他类里面进行访问?

Java Profiling & Profilers

Profiling Java Application with Systemtap

使用 profiling 工具查找 java 代码中 outOfMemoryError 的确切原因

JAVA中子类能不能继承父类的私有属性和方法?

hsqldb java的profiling示例程序