Подтвердить что ты не робот

Быстрее ли доступ к конечным локальным переменным, чем переменные класса в Java?

Я смотрел на некоторые из примитивных коллекций 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 будет делать по-другому, если бы первые три строки функции были прокомментированы.

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

4b9b3361

Ответ 1

Ключевое слово 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

Доступ к локальной переменной или параметру является одноступенчатой: возьмите переменную, расположенную со смещением 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.

Ответ 3

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

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

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

Ответ 4

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

Ответ 5

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

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