Объявить объект внутри или вне цикла?

Есть ли снижение производительности для следующего фрагмента кода?

for (int i=0; i<someValue; i++)
{
    Object o = someList.get(i);
    o.doSomething;
}

Или этот код на самом деле имеет больше смысла?

Object o;
for (int i=0; i<someValue; i++)
{
    o = someList.get(i);
    o.doSomething;
}

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

 erickson01 янв. 2009 г., 19:58
Двойной вопрос:stackoverflow.com/questions/110083/...
 radbyx28 февр. 2012 г., 23:08
+1. Спасибо за ответ, я как раз собирался спросить то же самое.
 serg21 дек. 2008 г., 18:38
Что если внутри цикла мы имеем: Object o = new Object (i)?
 Marc Gravell18 дек. 2008 г., 14:05
Кроме того, в C # эти два значения могут иметь совершенно разные значения: если «o» включено в анонимный метод / лямбду. Не публикуется в качестве ответа, так как это Java ;-p

Ответы на вопрос(16)

я обнаружил, что это очень эффективный способ решения проблем с призрачными потоками:

Object one;
Object two;
Object three;
Object four;
Object five;
try{
for (int i=0; i<someValue; i++)
{
o = someList.get(i);
o.doSomething;
}
}catch(e){
e.printstacktrace
}
finally{
one = null;
two = null;
three = null;
four = null;
five = null;
System.gc();
}
 Harry23 мая 2018 г., 01:26
Вы объявляете их недействительными, чтобы сказать GC, что они явно готовы к сбору?
 Petro24 мая 2018 г., 16:48
@Harry Это было несколько лет назад, но у тебя все получилось.System.gc() не всегда запускается сразу же По крайней мере, вы можете назначитьnull чтобы обеспечить надлежащую очистку.

Повторное использование объекта по «причинам производительности» часто неправильно.

Вопрос в том, что означает «объект»? ПОЧЕМУ ты это создаешь? Что это представляет? Объекты должны быть параллельны вещам реального мира. Вещи создаются, претерпевают изменения состояния и сообщают о своих состояниях по причинам.

Каковы эти причины? Как ваша объектная модель и отражает эти причины?

 Ilja Preuß18 дек. 2008 г., 19:16
В этих двух фрагментах вообще нет повторного использования объектов, просто повторное использование ссылочных переменных.
 S.Lott21 дек. 2008 г., 13:53
@saua: правда. Как показано в этом случае, перемещение ссылочной декларации случайным образом как «оптимизация».
 Joachim Sauer21 дек. 2008 г., 13:30
Концептуально это может быть то же самое, но это совершенно другой зверь в Java, и запутанные ссылки и объекты замедляют многих, многих начинающих в их понимании.
 S.Lott18 дек. 2008 г., 22:42
Ссылка - это, на мой взгляд, объект, первый класс, реальная сделка. Где это объявлено и как это объявлено, отражает значение, цель и намерение позади объекта.

сти, в которой он используется. Он предотвращает использование значений, назначенных в одной итерации, на более поздней итерации, это более оправданно

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

 Will Wagner18 дек. 2008 г., 14:10
С уважением, я думаю, что вы можете это перевернуть. Это первое, которое хранит переменную в области, в которой она используется.
 S.Lott18 дек. 2008 г., 14:11
-1: «имеет смысл» зависит от того, что программа должна «означать». Объекты существуют по причине. Что это за причина?
 Jack Ryan18 дек. 2008 г., 15:31
Будете ли вы правы, я перевернул его. Исправлено сейчас. С. Лотт: в этом примере я думаю, что очевидно, что объект существует для хранения значения, полученного из списка. Это не имеет смысла вне цикла.

я не знаю Java. Но эти два эквивалента? Время жизни объекта одинаково? В первом примере я предполагаю (не зная java), что o будет иметь право на сборку мусора сразу же после завершения цикла.

Но во втором примере, конечно же, o не будет иметь право на сборку мусора, пока не будет закрыта внешняя область (не показана)?

 vickirk15 февр. 2010 г., 12:57
o не будет иметь права на сборку мусора, пока он не будет удален из списка!

что на самом деле выдает компилятор. На моей установке (java 1.5 / mac, скомпилированный с eclipse), байт-код цикла идентичен.

 erickson01 янв. 2009 г., 20:00
Это не будет идентично в большем методе; действительная область действия переменной записана в таблице локальных переменных.

for(Object o : someList) {
    o.doSomething();
}

потому что это устраняет шаблон и проясняет намерение.

Если вы не работаете над встроенными системами, в этом случае все ставки отключены. В противном случае, не пытайтесь перехитрить JVM.

o переменная кfor блок. С точки зрения производительности, это может не иметь никаких эффектов в Java, но это может иметь место в компиляторах более низкого уровня. Они могут поместить переменную в регистр, если вы сделаете первый.

Фактически, некоторые люди могут подумать, что если компилятор тупой, второй фрагмент лучше с точки зрения производительности. Это то, что какой-то преподаватель сказал мне в колледже, и я смеялся над ним за это предложение! По сути, компиляторы выделяют память в стеке для локальных переменных метода только один раз в начале метода (путем корректировки указателя стека) и освобождают ее в конце метода (снова путем корректировки указателя стека, предполагая, что это не C ++ или нет деструкторов, которые можно было бы назвать). Таким образом, все локальные переменные на основе стека в методе выделяются сразу, независимо от того, где они объявлены и сколько памяти им требуется. На самом деле, если компилятор тупой, нет никакой разницы с точки зрения производительности, но если он достаточно умен, первый код на самом деле может быть лучше, поскольку он поможет компилятору понять область действия и время жизни переменной! Кстати, если он действительно умный, то не должно быть абсолютно никакой разницы в производительности, поскольку он выводит реальный объем.

Строительство объекта с использованиемnew Конечно, это совершенно не то, что просто объявить.

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

 Mehrdad Afshari18 дек. 2008 г., 16:29
Умные компиляторы обычно выделяют его, находя максимальное количество неразделенной памяти в каждом пути кода. В вашем примере это 2000 байтов, которые они выделяют. Если вы не использовали его, это не очень дорого, так как просто добавьте esp 2000 вместо add esp 200.
 Arkadiy18 дек. 2008 г., 15:35
Я не уверен насчет схемы распределения, которую вы описываете. Рассмотрим этот код: if (flag) {char buffer [100]; ...} else {char buffer [2000]; ...} В этом случае компилятору было бы очень глупо заранее выделить всю память. Вдвойне, если стек стоит премиум (встроенные приложения).

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

ле. Компилятор генерирует очень похожий байт-код и делает правильные оптимизации.

Смотреть статьюМиф - Определение переменных цикла внутри цикла ухудшает производительность для аналогичного примера.

Решение Вопроса

ной области видимости, потому что это намного более читабельно для следующего парня.

 EricSchaefer21 дек. 2008 г., 12:58
Также это предотвращает загрязнение пространства имен ...

что делает конструктор и что происходит с объектом после цикла, поскольку это в значительной степени определяет, как оптимизируется код.

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

 vickirk15 февр. 2010 г., 12:55
Здесь нет вызова конструктора, экземпляр получается из списка

Цитируя Кнута, который может цитировать Хоара:

Преждевременная оптимизация - корень всего зла.

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

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

иляции. Во втором случае o рассматривается как глобальный для цикла for, и в первом случае, умный компилятор Java знает, что o должен быть доступен для до тех пор, пока цикл длится и, следовательно, будет оптимизировать код таким образом, чтобы в каждой итерации не было переопределения типа o. Следовательно, в обоих случаях спецификация типа o будет выполняться один раз, а это означает, что единственная разница в производительности будет заключаться в объеме o. Очевидно, что более узкая область действия всегда повышает производительность, поэтому, чтобы ответить на ваш вопрос: нет, нет никакого снижения производительности для первого фрагмента кода; на самом деле этот фрагмент кода более оптимизирован, чем второй.

Во втором фрагменте o задается ненужная область, которая, помимо проблем с производительностью, также может быть проблемой безопасности.

Версия 1 очень предпочтительна - для понимания важно, чтобы область видимости была как можно более локальной. Также легче рефакторинг такого рода кода.

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

Передо мной код, который выглядит примерно так:

for (int i = offset; i < offset + length; i++) {
    char append = (char) (data[i] & 0xFF);
    buffer.append(append);
}
...
for (int i = offset; i < offset + length; i++) {
    char append = (char) (data[i] & 0xFF);
    buffer.append(append);
}
...
for (int i = offset; i < offset + length; i++) {
    char append = (char) (data[i] & 0xFF);
    buffer.append(append);
}

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

В качестве примечания, Java-приложения работают медленно. Я никогда не пытался выполнить профилирование в Java, но думаю, что снижение производительности происходит в основном из-за управления распределением памяти.

 Hot Licks17 апр. 2013 г., 16:54
Это тоже неправильно.
 sharakan17 апр. 2013 г., 16:30
Это не ответ на вопрос.
 annoyed17 апр. 2013 г., 17:07
Рад это слышать, но что именно не так?
 annoyed23 апр. 2013 г., 16:38
@sharakan, спасибо, но я просто попросил Hot Licks немного уточнить. И вы правы, это не совсем ответ, больше похоже на синтез того, что было сказано ранее, к которому я все еще отношусь скептически.
 sharakan19 апр. 2013 г., 18:55
@annoyed вы сделали два заявления без подтверждающих доказательств, не имеющих отношения к вопросу. Ваш первый вопрос о распределении стека, я не знаю, правильно это или нет. Ваш второй относительно производительности Java, безусловно, неправильно. Можно сказать, чтонемного Java-приложения работают медленно, ииногда Это происходит из-за управления распределением памяти, но это возможно для любой программы на любом языке, которая выделяет и освобождает память.

что реализации не-JVM могут работать по-другому, если это разрешено JLS ...]

Во-первых, имейте в виду, что локальная переменная "o" в примере является указателем, а не фактическим объектом.

Все локальные переменные размещаются в стеке времени выполнения в 4-байтовых слотах. двойные и длинные требуют двух слотов; другие примитивы и указатели берут один. (Даже логические выражения занимают полный слот)

Для каждого вызова метода должен быть создан фиксированный размер стека времени выполнения. Этот размер определяется максимальными локальными переменными "slots", необходимыми в любой данной точке метода.

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

В обоих случаях будет сгенерирован один и тот же байт-код, обновляющий один и тот же слот в стеке времени выполнения.

Другими словами, нет потери производительности вообще.

ОДНАКО, в зависимости от остальной части кода в методе, версия «объявления вне цикла» может фактически потребовать большего выделения стека времени выполнения. Например, сравнить

for (...) { Object o = ... }
for (...) { Object o = ... }

с

Object o;
for (...) {  /* loop 1 */ }
for (...) { Object x =...; }

В первом примере оба цикла требуют одинакового выделения стека во время выполнения.

Во втором примере, поскольку «o» живет за пределами цикла, для «x» требуется дополнительный слот стека времени выполнения.

Надеюсь, это поможет, - Скотт

Ваш ответ на вопрос