Какой цикл имеет лучшую производительность? Зачем?

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