Быстрее ли доступ к конечным локальным переменным, чем переменные класса в 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
.
Ответы
Ответ 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.