Java использует getter in for loop или создает локальную переменную?
У меня есть цикл for, который работает 4096 раз, и он должен быть как можно быстрее. Производительность здесь действительно важна. В настоящее время я использую методы getter внутри цикла, которые просто возвращают значения или объекты из полей, которые не изменяются во время цикла.
Пример:
for (;;) {
doSomething(example.getValue());
}
Есть ли накладные расходы с использованием геттеров? Быстро ли это происходит следующим образом?
Пример:
Object object = example.getValue();
for (;;) {
doSomething(object);
}
Если да, это также верно для доступа к публичным полям, например example.value
?
Изменение: я не использую System.out.println()
внутри цикла.
Изменение: некоторые поля не являются final
. Никакие поля volatile
и никакой метод (геттер) не synchronized
.
Ответы
Ответ 1
Как ответил Рожерио, получение ссылки на объект за пределами цикла (Object object = example.getValue();
) скорее всего будет быстрее (или, по крайней мере, никогда не будет медленнее), чем вызов геттера внутри цикла, потому что
- в "худшем" случае
example.getValue()
может на самом деле сделать некоторые очень вычислительно дорогие вещи в фоновом режиме, несмотря на то, что методы getter должны быть "тривиальными". Назначая ссылку один раз и повторно используя ее, вы делаете это дорогостоящее вычисление только один раз. - в "наилучшем" случае
example.getValue()
делает что-то тривиальное, такое как return value;
и поэтому назначение его внутри цикла будет не более дорогостоящим, чем вне цикла после того, как компилятор JIT привяжет код.
Однако более важным является разница в семантике между двумя и ее возможными эффектами в многопоточной среде: если состояние example
объекта изменяется таким образом, что вызывает example.getValue()
чтобы возвращать ссылки на разные объекты, это возможно, что на каждой итерации метод doSomething(Object object)
будет фактически работать с другим экземпляром Object
, напрямую вызвав doSomething(example.getValue());
, С другой стороны, вызывая getter вне цикла и устанавливая ссылку на возвращаемый экземпляр (Object object = example.getValue();
), doSomething(object);
будет работать с object
n раз для n итераций.
Это различие в семантике может привести к тому, что поведение в многопоточной среде будет радикально отличаться от поведения в однопоточной среде. Более того, это не обязательно актуальная проблема с многопоточными операциями "in-memory": если example.getValue()
зависит, например, от базы данных/HDD/сетевых ресурсов, возможно, что эти данные изменяются во время выполнения цикла, что позволяет что возвращается другой объект, даже если приложение Java является однопоточным. По этой причине лучше всего рассмотреть, что вы на самом деле хотите выполнить с помощью своего цикла, а затем выбрать вариант, который наилучшим образом отражает предполагаемое поведение.
Ответ 2
Это зависит от получателя.
Если это простой геттер, JIT в любом случае будет подключен к прямому полю, поэтому не будет заметной разницы. С точки зрения стиля используйте getter - это меньше кода.
Если получатель получает доступ к volatile
полю, появляется дополнительный доступ к памяти, поскольку значение не может храниться в регистре, однако его очень мало.
Если геттер synchronized
, то использование локальной переменной будет заметно быстрее, поскольку блокировки не нужно получать и освобождать каждый вызов, но код цикла будет использовать потенциально устаревшее значение поля в момент вызова получателя.
Ответ 3
Вы должны предпочесть локальную переменную за пределами цикла по следующим причинам:
- Это упрощает чтение/понимание кода, избегая вложенных вызовов методов, таких как
doSomething(example.getValue())
в одной строке кода, и позволяя коду давать более doSomething(example.getValue())
и более конкретное имя для значения возвращенный методом геттера. - Не все методы getter тривиальны (т.е. Иногда они выполняют некоторую потенциально дорогостоящую работу), но разработчики часто этого не замечают, полагая, что данный метод тривиален и недорог, когда это действительно не так. В таких случаях код может сильно пострадать, если разработчик не сможет его реализовать. Извлечение в локальную переменную имеет тенденцию избегать этой проблемы.
Ответ 4
Очень легко беспокоиться о производительности гораздо больше, чем это необходимо. Я знаю это чувство. Некоторые вещи, которые следует учитывать:
- 4096 не так много, поэтому, если это не закончится за очень короткое время, не беспокойтесь о производительности.
- Если в этом цикле есть что-то еще более дорогое, то геттер не имеет значения.
- Преждевременная оптимизация - это корень всего зла. Сосредоточьтесь на том, чтобы сделать код правильным и четким. Затем измерьте и профилируйте его и сузите самую дорогую вещь, и позаботьтесь об этом. При необходимости улучшите фактический алгоритм.
Что касается вашего вопроса, я не знаю точно, что делает JIT, но если он не может с уверенностью доказать, что example.getValue()
или example.value
не изменяется в цикле (что трудно сделать, если поле не является final
и геттер тривиален), то логически невозможно, чтобы он не мог неоднократно называть геттер в прежнем образце, поскольку это могло бы изменить поведение программы. Повторные звонки, безусловно, являются ненужным количеством дополнительной работы.
Сказав все это, создайте локальную переменную за пределами цикла, независимо от того, быстрее или быстрее она, потому что она понятна. Может быть, это вас удивляет, но хороший код не всегда самый короткий. Выразить намерение и другую информацию чрезвычайно важно. В этом случае локальная переменная за пределами цикла делает очевидным, чтобы кто-либо читал код, что аргумент doSomething
не изменяется (особенно, если вы делаете его окончательным), что полезно знать. В противном случае им, возможно, придется немного поработать, чтобы убедиться, что они знают, как ведет себя программа.
Ответ 5
Если вам нужно запустить его как можно быстрее, вы не должны использовать System.out.println
в критических разделах.
Что касается геттера: для использования геттера есть небольшие накладные расходы, но вы не должны беспокоиться об этом. У Java есть оптимизация getter и setter в JIT-компиляторе. Поэтому в конечном итоге они будут заменены собственным кодом.