Производительность и оптимизация метода доступа - PullRequest
7 голосов
/ 29 февраля 2012

Часто я сталкиваюсь с кодом, в котором метод Getter многократно используется / используется для получения какого-либо значения или передачи его в качестве параметра метода, например:

public class Test {
   public void someMethod() {
      if(person.getName() != null && person.getName().equalsIgnoreCase("Einstein")) {
           method1(person.getName());
      }
      method2(person.getName());
      method3(person.getName());
      method4(person.getName());
   }
}

Я обычно кодирую это, как показано ниже:

public class Test {
   public void someMethod() {
      String name = person.getName();
      if(name != null && name.equalsIgnoreCase("Einstein")) {
           method1(name);
      }
      method2(name);
      method3(name);
      method4(name);
   }

По моему мнению, при присвоении переменной переменной и ее использовании существует значительное преимущество в памяти и производительности, поскольку методы получения являются методами Java и используют стековые фреймы. Есть ли действительно значительное преимущество в кодировании таким образом? }

Ответы [ 6 ]

15 голосов
/ 29 февраля 2012

У вас в профиле ваше мнение в последнее время.

Производительность:

Возможно, это была микрооптимизация, о которой нужно было позаботиться в 1999-2001 годах в JVM до 1.2, и даже тогда я бы поставил под сомнение это, если бы некоторые серьезные цифры не показали иное.

Современные реализации JIT скажут вам, что сегодня ваше мнение неуместно.

Современные реализации компилятора делают все виды оптимизаций, которые делают мысли о таких вещах пустой тратой времени в Java. JIT просто делает его еще более бесполезной тратой.

Логика:

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

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

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

JVM встроит любые вызовы final членов экземпляра и удалит вызов метода, если в вызове метода нет ничего, кроме return this.name; Он знает, что в методе метода доступа нет логики, и он знает ссылку final, поэтому он знает, что может встроить значение, потому что оно не изменится.

С этой целью

person.getName() != null && person.getName().equalsIgnoreCase("Einstein") 

более корректно выражается как

person != null && "Einstein".equalsIgnoreCase(person.getName())

потому что нет шансов получить NullPointerException

Рефакторинг:

Современные инструменты рефакторинга IDE также устраняют любые аргументы о необходимости изменения кода в нескольких местах.

5 голосов
/ 29 февраля 2012

Разницы в производительности нет.Если это так, он просто мал.

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

Скажите, что на полпути кто-тозвонит setName и меняет имя.Теперь ваш код проходит совершенно неожиданный путь!На самом деле это было основной причиной нескольких исторических (даже на уровне ядра) уязвимостей безопасности.

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

2 голосов
/ 29 февраля 2012

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

Еще одно существенное отличие состоит в том, что getName() имеет побочный эффект (которого он не должен иметь, но вы не можете доверять другим классам вслепую), или если вы работаете в многопоточной системе. В первом случае разница очевидна, во втором случае другой поток может изменить значение, возвращаемое getName() во время выполнения оператора.

В обоих случаях вы, вероятно, захотите вызвать getName() только один раз.

2 голосов
/ 29 февраля 2012

Если вы полагаете, что что-то делаете в Java, потому что «оно может работать лучше», вы делаете это неправильно. JIT-компилятор оптимизирует тривиальный случай public String getName(){return name;}. Вместо этого вы должны принимать решения на основе: «Это правильный ООП способ сделать это»

С точки зрения ООП, используйте только геттер. Затем подкласс может переопределить метод по мере необходимости, чтобы сделать другие причудливые вещи (например, игнорировать имя «Эйнштейн»).

1 голос
/ 29 февраля 2012

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

if(person.expensiveOp() != null && person.expensiveOp().equalsIgnoreCase("Einstein")) {
       method1(person.expensiveOp());
  }
  method2(person.expensiveOp());
  method3(person.expensiveOp());
  method4(person.expensiveOp());

Кроме того, с помощью IDE с подсветкой синтаксиса я могу изолировать все случаи использования значения. Кто-то ответит, что вы также можете выделить все способы использования метода. Но может быть несколько вызовов одного и того же метода в разных экземплярах объекта (т. Е. toString())

0 голосов
/ 29 февраля 2012

Я провел некоторое исследование по этой теме, задав вопрос, и нашел то, что искал.Проблема заключалась в том, что я не использовал правильную терминологию при формулировании вопроса.' Inline Expansion ' (Подстройка компилятора) - вот что я искал.Вот цитата из Wiki:

В вычислительной технике встроенное расширение или встраивание - это ручная оптимизация или оптимизация компилятора, которая заменяет сайт вызова функции натело вызываемого.Эта оптимизация может улучшить использование времени и пространства во время выполнения за счет возможного увеличения конечного размера программы (т. Е. Размера двоичного файла).

Я также обнаружил, что эта тема уже обсуждаласьна этом сайте:

1) Влияют ли геттеры и сеттеры на производительность в C ++ / D / Java?

Вот цитата из приведенной выше ссылки:

В Java JIT-компилятор, вероятно, рано или поздно включит его.Насколько я знаю, JIT-компилятор JVM оптимизирует только интенсивно используемый код, поэтому вы могли видеть издержки при вызове функции изначально, пока getter / setter не был вызван достаточно часто .

2) Inlining In Java

...