举个例如这样的循环:
public boolean method(){
for (int i = 0; i < 5; i++) {
if (this.object.getSomething().getSomeArray().get(i).getArray().size() > 0)
return false;
}
return true;
}
每个get
方法只检索私有属性。相同代码的更易读的版本是:
public boolean method(){
MySomeArray mySomeArray = this.object.getSomething().getSomeArray();
for (int i = 0; i < 5; i++) {
MyArray array = mySomeArray.get(i).getArray();
if (array.size() > 0)
return false;
}
return true;
}
另一个版本是:
public boolean method(){
MySomeArray mySomeArray = this.object.getSomething().getSomeArray();
MyArray array;
for (int i = 0; i < 5; i++) {
array = mySomeArray.get(i).getArray();
if (array.size() > 0)
return false;
}
return true;
}
我知道在理论上编译器可以优化很多东西,在这种情况下,(在我看来)循环的三个版本应该在完全相同的机器代码中进行优化。 我是正确的还是三个版本中执行的指令数量会有所不同?
答案 0 :(得分:2)
如果MySomeArray
以及您的解除引用链中涉及的所有其他类都位于各自的类层次结构的底部,那么HotSpot将很容易将所有这些虚函数调用转换为“plain”(通过称为单态调用站点优化的技术进行非虚拟调用。
即使涉及的类不是叶类,也会发生这种情况。重要的是,在每个调用站点,只会调度一个对象类型。
由于虚函数的不确定性,编译器可以继续内联所有调用,然后执行任何进一步的优化,例如在您的情况下提升。从解除引用链中检索的最终值可以绑定到寄存器等。
请注意,上述大部分内容都取决于整个代码路径没有任何发生在之前与其他线程的操作的关系。在实践中,这主要意味着没有volatile
变量访问和没有synchronized
块(在您自己的代码中以及在您的代码中调用的所有代码中)。
答案 1 :(得分:1)
编写使用此方法的测试用例,并在运行时使用print the generated assembly code。然后,您可以检查自己有多少内部呼叫。我对编译器能够内联它们持怀疑态度,但JIT编译器可能会令人惊讶。
我更喜欢更易读的版本,因为它更具可读性。
答案 2 :(得分:1)
通过足够的内联,编译器确实可以将方法调用提升循环,就像你在第二和第三个例子中手动完成的那样。是否实际执行此操作的详细信息完全取决于所讨论方法的行为和大小,以及所涉及的JIT的复杂程度。
我写了你的例子并用Caliper测试了它,并且所有的方法都有相同的时间。我没有检查集会,因为那涉及更多 - 但我敢打赌它们几乎相当。
答案 3 :(得分:0)
麻烦的是你正在做出编译器无法做出的假设。
你知道this.object.getSomething().getSomeArray()
每次都不会改变循环,但编译器无法知道这一点。特别是因为其他线程可能同时修改这些变量......