访问最终局部变量比Java中的类变量更快吗?

job*_*job 14 java optimization micro-optimization

我一直在寻找一些Java基本集合(的宝库,fastutil,HPPC)和我已经注意到,类变量有时声明为图案final的局部变量.例如:

public void forEach(IntIntProcedure p) {
    final boolean[] used = this.used;
    final int[] key = this.key;
    final int[] value = this.value;
    for (int i = 0; i < used.length; i++) {
        if (used[i]) {
          p.apply(key[i],value[i]);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经完成了一些基准测试,看起来这样做会快一点,但为什么会这样呢?我试图理解如果函数的前三行被注释掉,Java将采取哪些不同的做法.

注意:这似乎与这个问题类似,但这是针对c ++的,并没有说明它们被声明的原因final.

Tom*_*icz 26

访问局部变量或参数是一步操作:获取位于堆栈上偏移量N的变量.如果你的函数有2个参数(简化):

  • N = 0 - this
  • N = 1 - 第一个参数
  • N = 2 - 第二个参数
  • N = 3 - 第一个局部变量
  • N = 4 - 第二局部变量
  • ...

因此,当您访问局部变量时,您有一个固定偏移量的内存访问(N在编译时已知).这是用于访问第一个方法参数(int)的字节码:

iload 1  //N = 1
Run Code Online (Sandbox Code Playgroud)

但是,当您访问字段时,您实际上正在执行额外的步骤.首先,您只是为了确定当前的对象地址而读取" 局部变量 " this.然后你加载一个getfield具有固定偏移量的field()this.因此,您执行两个内存操作而不是一个(或一个额外的).字节码:

aload 0  //N = 0: this reference
getfield total I  //int total
Run Code Online (Sandbox Code Playgroud)

因此,技术上访问局部变量和参数比对象字段更快.实际上,许多其他因素可能会影响性能(包括各种级别的CPU缓存和JVM优化).

final是一个不同的故事.它基本上是编译器/ JIT的一个提示,这个引用不会改变,所以它可以做一些更重的优化.但这很难追踪,尽可能使用经验法则final.

  • 我想这个答案(特别是它的最后一段)比标记的答案要好. (6认同)

Mik*_*uel 8

final关键字是这里的红鲱鱼.性能差异之所以出现是因为他们说了两件不同的事情.

public void forEach(IntIntProcedure p) {
  final boolean[] used = this.used;
  for (int i = 0; i < used.length; i++) {
    ...
  }
}
Run Code Online (Sandbox Code Playgroud)

是说,"取一个布尔数组,并为每个元素数组做一些事情."

如果没有final boolean[] used,该函数会说"当索引小于used当前对象字段当前值的长度时,获取当前对象字段的当前值并对used索引处的元素执行某些操作i."

JIT可能更容易证明循环绑定不变量以消除超额绑定检查等等,因为它可以更容易地确定导致值used变化的原因.即使忽略多个线程,如果p.apply可以改变值,used那么JIT也不能消除边界检查或做其他有用的优化.