【发布时间】:2014-06-14 18:32:27
【问题描述】:
通常,Java 会根据给定调用端遇到的实现数量来优化虚拟调用。这可以很容易地在我的benchmark 的results 中看到,当您查看myCode 时,这是一个返回存储的int 的简单方法。有个小事
static abstract class Base {
abstract int myCode();
}
有几个相同的实现,比如
static class A extends Base {
@Override int myCode() {
return n;
}
@Override public int hashCode() {
return n;
}
private final int n = nextInt();
}
随着实现数量的增加,方法调用的时间从两个实现的 0.4 ns 到 1.2 ns 增长到 11.6 ns,然后缓慢增长。当 JVM 看到多个实现时,即使用 preload=true,时间会略有不同(因为需要 instanceof 测试)。
到目前为止,一切都很清楚,但是,hashCode 的行为却截然不同。特别是在三种情况下,它的速度要慢 8-10 倍。知道为什么吗?
更新
我很好奇可怜的hashCode是否可以通过手动调度得到帮助,而且可以很多。
几个分支完美地完成了这项工作:
if (o instanceof A) {
result += ((A) o).hashCode();
} else if (o instanceof B) {
result += ((B) o).hashCode();
} else if (o instanceof C) {
result += ((C) o).hashCode();
} else if (o instanceof D) {
result += ((D) o).hashCode();
} else { // Actually impossible, but let's play it safe.
result += o.hashCode();
}
请注意,编译器会避免对两个以上的实现进行此类优化,因为大多数方法调用比简单的字段加载要昂贵得多,并且与代码膨胀相比增益会很小。
原始问题“为什么 JIT 不像其他方法那样优化 hashCode”仍然存在,hashCode2 证明它确实可以。
更新 2
看起来 bestsss 是对的,至少有这个注释
调用扩展 Base 的任何类的 hashCode() 与调用 Object.hashCode() 相同,如果在 Base 中添加显式 hashCode 会限制调用 Base 的潜在调用目标,这就是它在字节码中编译的方式。 hashCode().
我不完全确定发生了什么,但声明 Base.hashCode() 会使 hashCode 再次具有竞争力。
更新 3
好的,提供Base#hashCode 的具体实现会有所帮助,但是,JIT 必须知道它永远不会被调用,因为所有子类都定义了自己的(除非加载了另一个子类,这可能导致去优化,但这对 JIT 来说并不是什么新鲜事)。
所以看起来错过了优化机会 #1。
提供Base#hashCode 的抽象 实现的工作原理相同。这是有道理的,因为它确保不需要进一步查找,因为每个子类都必须提供自己的(它们不能简单地从祖父母继承)。
对于两个以上的实现,myCode 的速度要快得多,以至于编译器必须做一些不太理想的事情。也许错过了优化机会 #2?
【问题讨论】:
-
我会强调 Base 的多种实现以及此类的扩展;它被埋没在问题中(但根本不在标题中)并且感觉大部分都迷失了。
-
您使用的是哪个版本的卡尺?我想自己测试一下。
-
@SotiriosDelimanolis 我正在使用来自git 的那个,但我确信它可以很容易地适应另一个(它只是设置和时间*方法)。
-
@user3580294 您在上面看到的所有短条实际上都来自避免虚拟方法表查找。最短的一个(0.4 ns,即每个方法调用一个周期)只有在 JVM 知道只有一个实现并直接内联读取的字段时才有可能。第二个最短的(0.6 ns)还包含一个正确预测的分支测试,
o实际上是一个A实例。第三个最短的(1.2 ns)来自内联A.myCode()和B.myCode()之间的切换。当桌子真正参与进来时,就会出现 10 倍的减速。 -
@user3580294 覆盖对性能的影响可能很大。 javaspecialists.eu/archive/Issue158.html@maaartinus 上的更多信息关于实际问题:
hashCode方法经过 JIT 的特殊处理。你可能会玩-XX:DisableIntrinsic=_hashCode,或者看看hg.openjdk.java.net/jdk8/jdk8/hotspot/file/a57a165b8296/src/…(和#l3977、#l4000、#l4103 ...搜索“hashCode”)。我无法指出原因,但猜测它隐藏在其中的某个地方......
标签: java performance hashcode