Это быстрее, чтобы получить доступ к конечным локальным переменным, чем переменные класса в Java? - PullRequest
14 голосов
/ 07 июля 2011

Я просматривал некоторые из примитивных java-коллекций ( trove , 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]);
        }
    }
}

Я провел некоторый бенчмаркинг, и кажется, что при этом он немного быстрее, но почему это так?Я пытаюсь понять, что делает Java по-другому, если первые три строки функции были закомментированы.

Примечание: Это похоже на этот вопрос , но это было для c ++ ине рассматривается, почему они объявлены final.

Ответы [ 5 ]

26 голосов
/ 07 июля 2011

Доступ к локальной переменной или параметру является одношаговой операцией: возьмите переменную, расположенную со смещением N в стеке. Если ваша функция имеет 2 аргумента (упрощенно):

  • N = 0 - this
  • N = 1 - первый аргумент
  • N = 2 - второй аргумент
  • N = 3 - первая локальная переменная
  • N = 4 - вторая локальная переменная
  • ...

Так, когда вы обращаетесь к локальной переменной, у вас есть один доступ к памяти с фиксированным смещением (N известно во время компиляции). Это байт-код для доступа к первому аргументу метода (int):

iload 1  //N = 1

Однако, когда вы получаете доступ к полю, вы фактически делаете дополнительный шаг. Сначала вы читаете « локальная переменная » this просто для определения текущего адреса объекта. Затем вы загружаете поле (getfield), которое имеет фиксированное смещение от this. Таким образом, вы выполняете две операции с памятью вместо одной (или одной дополнительной). Bytecode:

aload 0  //N = 0: this reference
getfield total I  //int total

Таким образом, технический доступ к локальным переменным и параметрам происходит быстрее, чем к объектным полям. На практике на производительность могут влиять многие другие факторы (в том числе различные уровни оптимизации кэша ЦП и JVM).

final - это другая история. Это в основном подсказка для компилятора / JIT, что эта ссылка не изменится, поэтому она может сделать более тяжелые оптимизации. Но это гораздо сложнее отследить, как правило, при большом пальце руки final всегда, когда это возможно.

8 голосов
/ 07 июля 2011

Ключевое слово final здесь - красная сельдь. Разница в производительности объясняется тем, что они говорят две разные вещи.

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

говорит: «Извлеките логический массив, и для каждого элемента этот массив что-то сделает».

Без final boolean[] used функция говорит: «пока индекс меньше длины текущего значения поля used текущего объекта, извлеките текущее значение поля used текущего объекта и что-то сделать с элементом по индексу i. "

JIT может намного быстрее проверять инварианты, связанные с циклом, чтобы исключить проверки избыточных границ и т. Д., Поскольку он может намного легче определить, что может привести к изменению значения used. Даже если игнорировать несколько потоков, если p.apply может изменить значение used, то JIT не сможет устранить проверки границ или выполнить другие полезные оптимизации.

2 голосов
/ 07 июля 2011

сообщает среде выполнения (jit), что в контексте вызова этого метода эти 3 значения никогда не изменятся, поэтому среде выполнения не нужно постоянно загружать значения из переменной-члена.это может дать небольшое улучшение скорости.

конечно, поскольку джит становится умнее и может самостоятельно разобраться в этих вещах, эти соглашения становятся менее полезными.

обратите внимание, я не сделалдайте понять, что ускорение больше связано с использованием локальной переменной, чем с последней частью.

1 голос
/ 07 июля 2011

В сгенерированных кодах операций VM локальные переменные являются записями в стеке операндов, а ссылки на поля должны быть перемещены в стек с помощью инструкции, которая извлекает значение через ссылку на объект. Я полагаю, что JIT может упростить регистрацию ссылок в стеке.

0 голосов
/ 07 июля 2011

Такие простые оптимизации уже включены в среду выполнения JVM. Если JVM делает наивный доступ к переменным экземпляра, наши Java-приложения будут работать медленно.

Такая ручная настройка, вероятно, полезна для более простых JVM, например, Android.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...