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

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

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

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;
}

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

4b9b3361

Ответ 1

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

Ответ 2

Чтобы процитировать Кнута, который может цитировать Hoare:

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

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

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

Ответ 4

Вы можете разобрать код с помощью javap -c и проверить, что испускает компилятор. В моей настройке (java 1.5/mac скомпилировано с eclipse) байт-код для цикла идентичен.

Ответ 5

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

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

Построение объекта с использованием new полностью отличается от простого объявления его, конечно.

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

Ответ 6

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

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

Ответ 7

Не следует преждевременно оптимизировать. Лучше, чем любой из них:

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

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

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

Ответ 8

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

Ответ 9

Они имеют разную семантику. Что более значимо?

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

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

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

Ответ 10

Чтобы понять суть этого вопроса... [Обратите внимание, что реализация не-JVM может делать что-то по-другому, если разрешено JLS...]

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

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

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

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

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

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

В КАЧЕСТВЕ, в зависимости от остальной части кода в методе, версия "объявление за пределами цикла" может потребовать большего распределения стека во время выполнения. Например, сравните

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

с

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

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

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

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

Ответ 11

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

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

Ответ 12

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

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

Ответ 13

Как человек, который поддерживает больше кода, чем пишет код.

Версия 1 является очень предпочтительной - сохранение возможности как можно более локальной является необходимой для понимания. Его также проще реорганизовать такой код.

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

Ответ 14

При использовании нескольких потоков (если вы делаете 50+), я обнаружил, что это очень эффективный способ обработки проблем с призрачными потоками:

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();
}

Ответ 15

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

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

Ответ 16

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

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, но я думаю, что поражение производительности происходит в основном из управления распределением памяти.