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

Java pattern: когда имеет смысл использовать временные переменные

Поэтому я часто нахожу, что делаю что-то вроде следующего шаблона. Вместо:

if (map.containsKey(someKey)) {
    Value someValue = map.get(someKey);
    ...
}

Чтобы не пересекать карту дважды (и поскольку я знаю, что моя карта не хранит нули), я сделаю:

Value someValue = map.get(someKey);
if (someValue != null) {
    ...
}

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

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

if (someObject.someMethod() != null) {
    processSomehow(someObject.someMethod());
}

Я знаю, что это граничит с "неконструктивным" вопросом, поэтому я ищу ответы, в которых представлены некоторые факты и ссылки, а не просто гипотеза.

  • Когда имеет смысл делать это, а когда нет?
  • Как мне оценить "стоимость" someMethod(), чтобы определить, когда должна использоваться временная переменная?
  • Для простых методов, таких как методы get, возможно, это мешает компилятору или оптимизатору hotspot и фактически создает менее эффективный код?

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

Спасибо за любую информацию.

4b9b3361

Ответ 1

Как мне оценить "стоимость" метода someMethod(), чтобы определить, когда должна использоваться временная переменная?

Просто посмотрите на реализацию someMethod(). Если он просто возвращает поле - как обычно, будет использовать метод getter, нет необходимости объявлять локальную переменную (производительность мудрая). Если метод создает объекты, вызывает базу данных или читает содержимое файла, тогда обычно разумно кэшировать возвращаемое значение.

Лично я не забочусь о проблемах производительности, вызванных объявлением временной переменной. Я просто сохраняю возвращаемое значение вместо вызова метода дважды. Код намного легче читать и понимать (если вы не назовете все эти переменные просто temp;-))

Ответ 2

Понятно, что я не могу дважды вызвать someMethod(), если есть побочные эффекты, но когда имеет смысл только называть его один раз с оптимизационной точки зрения?

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

Ответ 3

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

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

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

Ответ 4

Для меня все это сводится к логике программы, а не к производительности. Итак,

if (map.containsKey(someKey)) {
    Value someValue = map.get(someKey);
    ...
}

не эквивалентен этому коду во всех случаях:

Value someValue = map.get(someKey);
if (someValue != null) {
    ...
}

Рассмотрим карту, где значение может быть нулевым для данного ключа. Если часть ... null безопасна, тогда код ведет себя по-другому.

Также этот код с каким-то образом имеет логические проблемы. Рассмотрим это:

BufferedRead reader = ...;
if (reader.readLine() != null) {
    processSomehow(reader.readLine());
}

Здесь ясно, что readLine нужно вызывать дважды. Другой проблемный домен для этого типа кода - многопоточность: код ожидает, что значение не изменится, но другой поток может сделать это успешно.

Резюме Сохраняйте код как четкий и правильный, насколько это возможно. Это означает, что если вы ожидаете, что значение не изменится и вам понадобится его несколько раз, используйте переменную, чтобы сказать кому-либо это.

Ответ 5

Несколько человек цитируют Knuth (неявно или явно), но я думаю, что это всегда имеет смысл в качестве шаблона проектирования (и случаи, когда null является допустимым ключом, как указано out by AH становятся намного понятнее). На мой взгляд, это похоже на случай передачи переменной по ссылке const в С++ вместо значения. Для этого есть две причины: (а) он сообщает, что значение не изменится, но основная причина, по которой это делается, заключается в том, что он немного быстрее. Это не имеет значения для большинства индивидуальных вызовов, но когда каждая процедура передает каждый параметр по значению, вы замечаете это. Это хорошая привычка быть. Случай, который вы упомянули, не так распространен, и поэтому он не так важен, но я бы сказал, что это хорошая привычка.

Ответ 6

Напоминает мне о том, как использовать стиль использования .NET Dictionary: Что более эффективно: Словарь TryGetValue или ContainsKey + Item?

В целом, как указано в комментариях, вы не знаете горячие точки производительности в своей программе, пока не запустите ее с реальными данными под профилировщиком.

Кроме того, компиляторы на императивных языках не могут оптимизировать повторные вызовы из-за возможных побочных эффектов. Каждый вызов функции может возвращать новое значение. И, кстати, это также одна из причин, почему вы сами не должны полагаться на повторные звонки. Сегодня, может быть, вы знаете, что функция или свойство свободны от побочных эффектов, а повторные вызовы возвращают одно и то же значение, но завтра вы меняете его и добавляете некоторый случайный генератор внутри, и вся логика с повторными вызовами будет нарушена.