Объявить объект внутри или вне цикла?
есть ли штраф за производительность для следующего фрагмента кода?
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;
}
Если в байтовом коде эти два полностью эквивалентны, то, очевидно, первый метод выглядит лучше с точки зрения стиля, но я хочу убедиться, что это так.
16 ответов:
в современных компиляторах нет. Я объявляю объекты в самой маленькой области, которую я могу, потому что это намного более читаемо для следующего парня.
цитировать кнута, который может цитировать Хоара:
преждевременная оптимизация является корнем всего зла.
будет ли компилятор производить незначительно более быстрый код, определяя переменную вне цикла, является спорным, и я предполагаю, что это не будет. я бы Угадай он будет производить идентичный байт-код.
сравните это с количеством ошибок, которые вы, вероятно, предотвратите, правильно определив свой переменная, использующая объявление в цикле...
для объявления объекта o в цикле нет штрафа за производительность. Компилятор генерирует очень похожий байт-код и делает правильные оптимизации.
смотрите статью миф-определяющие переменные цикла внутри цикла плохо влияют на производительность для аналогичного примера.
вы можете разобрать код с помощью javap-c и проверить, что на самом деле выдает компилятор. В моей настройке (java 1.5/mac, скомпилированный с eclipse) байт-код для цикла идентичен.
первый код лучше, так как он ограничивает рамки
oпеременнойforблок. С точки зрения производительности он может не иметь никаких эффектов в Java, но он может иметь в компиляторах более низкого уровня. Они могут поместить переменную в регистр, если вы сделаете первый.В самом деле, некоторые люди могут подумать, что если компилятор тупой, второй фрагмент лучше с точки зрения производительности. Это то, что какой-то инструктор сказал мне в колледже, и я смеялся над ним за это предложение! В принципе, компиляторы выделяют память в стеке для локальных переменных метода только один раз в начале метода (путем настройки указателя стека) и освобождают его в конце метода (снова путем настройки указателя стека, предполагая, что это не C++ или у него нет деструкторов для вызова). Таким образом, все локальные переменные на основе стека в методе выделяются сразу, независимо от того, где они объявлены и сколько памяти им требуется. На самом деле, если компилятор тупой, есть нет разницы с точки зрения производительности, но если он достаточно умен, первый код действительно может быть лучше, поскольку он поможет компилятору понять область и время жизни переменной! Кстати, если он действительно умный, не должно быть абсолютно никакой разницы в производительности, поскольку он выводит фактический объем.
построение объекта с помощью
newполностью отличается от простого объявления, конечно.Я думаю, что читаемость более важна, чем производительность и с точки зрения читаемости, первый код определенно лучше.
Я должен признать, что не знаю java. Но являются ли эти два эквивалента? Являются ли жизни объекта одинаковыми? В первом примере я предполагаю (не зная java), что o будет иметь право на сбор мусора сразу же после завершения цикла.
но во втором примере, конечно, o не будет иметь права на сбор мусора до тех пор, пока не будет выведена внешняя область (не показана)?
не оптимизируйте преждевременно. Лучше, чем любой из них:
for(Object o : someList) { o.doSomething(); }потому что он устраняет шаблонный и уточняет намерение.
Если вы не работаете на встроенных системах, в этом случае все ставки выключены. В противном случае, не пытайтесь перехитрить JVM.
Я всегда думал, что большинство компиляторов эти дни достаточно умны, чтобы сделать последний вариант. Предполагая, что это так, я бы сказал, что первый тоже выглядит лучше. Если цикл становится очень большим, нет необходимости искать все вокруг, где объявлено o.
Они имеют различную семантику. Что более значимо?
повторное использование объекта по "причинам производительности" часто неправильно.
вопрос в том, что объект "означает"? Зачем вы его создаете? Что она собой представляет? Объекты должны быть параллельны вещам реального мира. Вещи создаются, претерпевают изменения состояния и сообщают о своих состояниях по причинам.
каковы эти причины? Как ваша объектная модель и отражает эти причины?
чтобы добраться до сути этого вопроса... [Обратите внимание, что реализации, отличные от JVM, могут делать все по-другому, если это разрешено JLS...]
все локальные переменные размещаются в стеке времени выполнения в 4-байтовых слотах. двойные и длинные требуют двух слотов; другие примитивы и указатели берут один. (Даже логические значения занимают полный слот)
фиксированный размер стека времени выполнения должен быть создан для каждого вызова метода. Этот размер определяется максимальной локальной переменной "слоты", необходимой в любом заданном месте в методе.
в обоих случаях будет сгенерирован один и тот же байт-код, обновляющий один и тот же слот в стеке времени выполнения.
другими словами, никакого штрафа за производительность вообще.
однако, в зависимости от остальная часть кода в методе, версия "объявление вне цикла" может фактически потребовать большего выделения стека времени выполнения. Например, сравните
for (...) { Object o = ... } for (...) { Object o = ... }С
Object o; for (...) { /* loop 1 */ } for (...) { Object x =...; }в первом примере оба цикла требуют одного и того же распределения стека времени выполнения.
во втором примере, поскольку "o "живет за циклом," x " требует дополнительного слота стека времени выполнения.
надеюсь, это поможет, -- Скотт
в обоих случаях информация о типе объекта o определяется при компиляции time.In второй экземпляр, o рассматривается как глобальный для цикла for, и в первом случае умный компилятор Java знает, что o должен быть доступен до тех пор, пока цикл длится, и, следовательно, оптимизирует код таким образом, что в каждой итерации не будет никакой респецификации типа o. Следовательно, в обоих случаях спецификация типа o будет выполнена один раз, что означает единственную производительность разница будет заключаться в области o. очевидно, что более узкая область всегда повышает производительность, поэтому, чтобы ответить на ваш вопрос: нет, для первого кода snip нет штрафа за производительность; на самом деле этот код snip более оптимизирован, чем второй.
во втором СНиП, о том, что дал ненужный объем, который, помимо того, что проблемы с производительностью, может быть также и вопросом безопасности.
первый имеет гораздо больше смысла. Он сохраняет переменную в области, в которой она используется. и предотвращает использование значений, назначенных в одной итерации, в более поздней итерации, это более защитно.
первый иногда говорят, что он более эффективен, но любой разумный компилятор должен быть в состоянии оптимизировать его, чтобы быть точно таким же, как последний.
как тот, кто поддерживает больше кода, чем пишет код.
Версия 1 является гораздо предпочтительнее-сохранение области как можно более локальным имеет важное значение для понимания. Его также легче рефакторинг такого рода кода.
Как обсуждалось выше-я сомневаюсь, что это будет иметь какое-либо значение в эффективности. На самом деле я бы сказал, что если область более локальна, компилятор может сделать с ней больше!
при использовании нескольких потоков (если вы делаете 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(); }
ответ частично зависит от того, что делает конструктор и что происходит с объектом после цикла, поскольку это в значительной степени определяет, как оптимизируется код.
Если объект большой и сложный, абсолютно объявить его вне цикла. В противном случае люди, говорящие вам не преждевременно оптимизировать, правы.
Я на самом деле передо мной код, который выглядит так:
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); }Итак, полагаясь на способности компилятора, я могу предположить, что будет только одно распределение стека для Я и добавить. Тогда все будет хорошо, кроме дублированного кода.
в качестве примечания, java-приложения, как известно, медленно. Я никогда не пытался делать профилирование на java, но я думаю, что хит производительности происходит в основном от выделения памяти управление.
Comments