Какой цикл имеет лучшую производительность? Зачем?
String s = "";
for(i=0;i<....){
s = some Assignment;
}
или
for(i=0;i<..){
String s = some Assignment;
}
Мне больше не нужно использовать 's' вне цикла.
Первый вариант, возможно, лучше, поскольку новая строка не инициализируется каждый раз. Второй, однако, приведет к тому, что область действия переменной будет ограничена самим циклом.
РЕДАКТИРОВАТЬ: В ответ на ответ Милхоуза. Было бы бессмысленно назначать String константе в цикле, не так ли? Нет, здесь "Some Assignment" означает изменение значения, полученного из перечня, переименованного через.
Кроме того, вопрос заключается не в том, что я беспокоюсь об управлении памятью. Просто хочу знать, что лучше.
Ответы
Ответ 1
Ограниченный охват лучше
Используйте второй вариант:
for ( ... ) {
String s = ...;
}
Область влияния не влияет на производительность
Если вы разбираете код, скомпилированный из каждого (с помощью инструмента JDK javap
), вы увидите, что в обоих случаях цикл компилируется в соответствии с теми же инструкциями JVM. Также обратите внимание, что Брайан Р. Бонди "Вариант № 3" идентичен Варианту №1. Ничего лишнего не добавляется или не удаляется из стека при использовании более жесткой области видимости, и в обоих случаях эти же данные используются в стеке.
Избегать преждевременной инициализации
Единственное различие между двумя случаями состоит в том, что в первом примере переменная s
излишне инициализирована. Это отдельная проблема из местоположения объявления переменной. Это добавляет две неиспользуемые инструкции (для загрузки строковой константы и сохранения ее в слоте фрейма стека). Хороший инструмент статического анализа предупредит вас о том, что вы никогда не читаете значение, которое вы назначаете s
, и хороший компилятор JIT, вероятно, выйдет из него во время выполнения.
Вы можете исправить это просто, используя пустое объявление (т.е. String s;
), но это считается плохой практикой и имеет следующий побочный эффект, обсуждаемый ниже.
Часто поддельное значение, подобное null
, присваивается переменной просто для того, чтобы замалчивать ошибку компилятора, которую переменная считывает без инициализации. Эта ошибка может быть воспринята как подсказка о том, что область переменных слишком велика и что она объявляется до того, как она понадобится для получения действительного значения. Пустые объявления заставляют вас рассматривать каждый путь кода; не игнорируйте это ценное предупреждение, назначая фиктивное значение.
Сохранять слоты стека
Как уже упоминалось, в то время как инструкции JVM одинаковы в обоих случаях, есть тонкий побочный эффект, который позволяет на уровне JVM лучше использовать максимально возможную область действия. Это видно в "локальной таблице переменных" для метода. Подумайте, что произойдет, если у вас несколько циклов с переменными, объявленными в излишне большой области:
void x(String[] strings, Integer[] integers) {
String s;
for (int i = 0; i < strings.length; ++i) {
s = strings[0];
...
}
Integer n;
for (int i = 0; i < integers.length; ++i) {
n = integers[i];
...
}
}
Переменные s
и n
могут быть объявлены внутри своих соответствующих циклов, но поскольку они не являются, компилятор использует два "слота" в кадре стека. Если они были объявлены внутри цикла, компилятор может повторно использовать один и тот же слот, делая меньший размер кадра стека.
Что действительно важно
Однако большинство этих вопросов несущественны. Хороший компилятор JIT увидит, что невозможно прочитать начальное значение, которое вы тратили впустую, и оптимизировать присвоение. Сохранение слота здесь или не будет делать или прерывать ваше приложение.
Важно, чтобы ваш код был читабельным и легким в обслуживании, и в этом отношении использование ограниченного объема явно лучше. Чем меньше область видимости переменной, тем проще понять, как она используется и какое влияние окажут какие-либо изменения в коде.
Ответ 2
В теории, это пустая трата ресурсов, чтобы объявить строку внутри цикла.
На практике, однако, оба представленных вами фрагмента будут скомпилированы до одного и того же кода (объявление вне цикла).
Итак, если ваш компилятор делает любую оптимизацию, нет никакой разницы.
Ответ 3
В общем, я бы выбрал второй, потому что область действия переменной 's' ограничена циклом. Преимущества:
- Это лучше для программиста, потому что вам не нужно беспокоиться о том, что 's' используется снова где-то позже в функции
- Это лучше для компилятора, потому что область действия переменной меньше, и поэтому она потенциально может сделать больше анализа и оптимизации.
- Это лучше для будущих читателей, потому что они не будут задаваться вопросом, почему переменная 's' объявлена вне цикла, если она никогда не использовалась позже
Ответ 4
Если вы хотите ускорить цикл, я предпочитаю объявлять максимальную переменную рядом с счетчиком, чтобы не требовалось повторного поиска для condidtion:
вместо
for (int i = 0; i < array.length; i++) {
Object next = array[i];
}
Я предпочитаю
for (int i = 0, max = array.lenth; i < max; i++) {
Object next = array[i];
}
Любые другие вещи, которые следует учитывать, уже упоминались, так что просто мои два цента (см. сообщение ericksons)
Greetz, GHad
Ответ 5
Чтобы добавить немного к @ответу Эстебана Арайи, они оба потребуют создания новой строки каждый раз через цикл (в качестве возвращаемого значения some Assignment
). Эти строки должны быть собраны в любом виде.
Ответ 6
Я знаю, что это старый вопрос, но я думал, что добавлю немного, что немного связано.
Я заметил, что при просмотре исходного кода Java некоторые методы, такие как String.contentEquals(дублированные ниже), делают избыточные локальные переменные просто копиями переменных класса. Я считаю, что где-то был комментарий, который подразумевал, что доступ к локальным переменным быстрее, чем доступ к переменным класса.
В этом случае "v1" и "v2" кажутся ненужными и могут быть устранены для упрощения кода, но были добавлены для повышения производительности.
public boolean contentEquals(StringBuffer sb) {
synchronized(sb) {
if (count != sb.length())
return false;
char v1[] = value;
char v2[] = sb.getValue();
int i = offset;
int j = 0;
int n = count;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
}
return true;
}
Ответ 7
Мне кажется, что нам нужна дополнительная спецификация проблемы.
s = some Assignment;
не указано, какое это назначение. Если задание
s = "" + i + "";
тогда необходимо назначить новое жало.
но если это
s = some Constant;
s просто укажет на место памяти констант, и, следовательно, первая версия будет более эффективной с точки зрения памяти.
Мне кажется, немного глупо беспокоиться о большей оптимизации цикла for для интерпретируемого языка IMHO.
Ответ 8
Когда я использую несколько потоков (50+), я обнаружил, что это очень эффективный способ обработки проблем с призрачными потоками, не имея возможности правильно закрыть процесс. Если я ошибаюсь, я знаю, почему я ошибаюсь:
Process one;
BufferedInputStream two;
try{
one = Runtime.getRuntime().exec(command);
two = new BufferedInputStream(one.getInputStream());
}
}catch(e){
e.printstacktrace
}
finally{
//null to ensure they are erased
one = null;
two = null;
//nudge the gc
System.gc();
}