【问题标题】:Do Java compilers commonly precompute hashcodes of final fields?Java 编译器通常会预先计算最终字段的哈希码吗?
【发布时间】:2013-09-27 10:07:15
【问题描述】:

我有一个 HashMap 密集型 Java 程序,其中几个类的哈希码从 final 字段计算而来。例如:

public class Foo {
    private final int bar;
    private final String zot;

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + bar;
        result = prime * result + zot.hashCode();
        return result;
    }
}

编译器可能会观察到哈希码在对象初始化后无法更改,并将其预先计算到额外的private final 字段中。当前的 Java 编译器是否执行此操作,例如 Oracle JDK 7 中的编译器?我可以分解.class 文件,但是JIT 也可能在运行时进行这种优化,我不会在那里看到它。无论如何,我对除此之外的其他情况感兴趣,因此如果能找到一种通用方法来识别编译器自动执行的任何优化,那就太好了。

【问题讨论】:

  • 如果对象从未真正用于 HashMap 或其他任何调用其 hashCode 方法的对象,那么您所描述的是反优化。
  • 编译器无法做到这一点,但有一个ongoing discussion 将它引入Lombok
  • @LouisWasserman:JIT 分析应用程序的热门方法并开始移动它们是很常见的。例如,任何专门访问静态和私有字段的方法都可以内联到任何频繁调用者中。这是一个昂贵的优化,所以它只发生在被观察为热的方法上。但是我想知道的是,我们怎样才能知道 JIT 做了哪些优化呢?如果有一个文档,或者以某种方式对其进行内省,那就太好了。

标签: java optimization compiler-construction


【解决方案1】:

当前的 Java 编译器是否会执行此操作,例如 Oracle JDK 7 中的编译器?

javac 几乎没有优化。

我可以分解 .class 文件,

您可能不喜欢您在优化方面看到的内容。 ;)

但是 JIT 也可能在运行时进行这种优化,我不会在那里看到它。

如果 JIT 确实对此进行了优化,您将看不到它,实际上它并没有这样做。这就是为什么 String 在运行时缓存它的 hashCode(),在代码中显式。

【讨论】:

  • 酷,谢谢。但是你怎么知道?下次我有这种问题时,如果有资源就好了。
  • @ByronHawkins 我对标题和数据存储区域有所了解。没有缓存用户生成的 hashCode 的空间,String 必须明确执行,如果 JIT 为您执行,则不会这样做。
  • @ByronHawkins:每个对象浪费额外的四个字节可能对分配大量这些对象的程序有害。因此,对于 JVM 来说,这并不是一件真正安全或理智的事情。
  • @PeterLawrey:没错,但是如果你在对象位于 HashMap 中时以这种方式更改对象,你就是在玩火。那种想要燃烧和毁容你可怕的火焰。
  • @PeterLawrey:我不确定我是否了解类头。编译器和 JIT 都可以添加字段,就像开发人员直接将它们放在类中一样。这是 Java 相对于 C/C++ 等具有可见指针的语言的优势,其中编译器无法添加字段或更改内存布局。我们怎么知道 JIT 不会将 hashCode() 计算移到构造函数中,如果它的分析器显示该方法是热的?
【解决方案2】:

不,编译器不这样做。但是,自己将哈希码存储在一个字段中并引用它而不是一直重新计算并不难:

private int hash = -1;

public int hashCode() {
    if (hash == -1) {
        // compute hash, assign it to the hash variable, and return it
    } else {
        return hash;
    }
}

这种方法其实是String类采用的,你检查一下它的source就可以看到:

121  /** Cache the hash code for the string */
122  private int hash; // Default to 0

【讨论】:

  • 谢谢——但你怎么知道的?下次我有这种问题时,如果有资源就好了。
  • @ByronHawkins 您可以使用javap 查看字节码,正如另一个答案所建议的那样。
  • @zhong.j.yu 这应该没问题。请详细说明。
  • @zhong.j.yu 没有同步或锁定,但它是线程安全的。 java.lang.String 中的实现也没有同步或锁定,这证明了这一点。唯一的危害是相同的hash 值可能偶尔会被计算多次,这不值得锁定。
  • jeremymanson.blogspot.com/2008/12/…(向下滚动到“现在,让我们破解密码”
【解决方案3】:

编译器可以为任何合适的方法执行此操作,而不仅仅是hashCode(),,但事实并非如此。可以通过javap -p自己查看是否有添加字段,也可以通过javap -c查看字节码。

但是您发布的方法不是合适的候选人。 String.hashCode() 在运行时的实现可能与编译器可用的不同。编译器不能假设它不是。

【讨论】:

  • +1 我没有想到你在第二段中提出的观点。
  • 可以进行优化——编译器只是将hashCode() 计算移动到构造函数中,因此它在运行时会使用正确的值进行计算。 JIT 内联热方法是很常见的,所以如果它看到对像 hashCode() 这样的明显静态方法的过度调用,JIT 肯定有可能重建类。仍在寻找一种方法来找出哪些优化实际上是在实践中完成的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-04
  • 1970-01-01
  • 1970-01-01
  • 2011-12-08
  • 1970-01-01
  • 2017-02-20
  • 2022-10-02
相关资源
最近更新 更多