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

StackOverflow https://stackoverflow.com/questions/110083

Вопрос

String s = "";
for(i=0;i<....){
    s = some Assignment;
}

или

for(i=0;i<..){
    String s = some Assignment;
}

Мне больше не нужно использовать 's' вне цикла.Первый вариант, пожалуй, лучше, поскольку новая строка не инициализируется каждый раз.Однако второй вариант приведет к тому, что область действия переменной будет ограничена самим циклом.

РЕДАКТИРОВАТЬ:В ответ на ответ Милхауса.Было бы бессмысленно присваивать строку константе внутри цикла, не так ли?Нет, здесь «некоторое присвоение» означает изменяющееся значение, полученное из перебираемого списка.

Кроме того, вопрос не в том, что меня беспокоит управление памятью.Просто хочу знать, что лучше.

Это было полезно?

Решение

Ограниченный объем лучше всего

Используйте второй вариант:

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

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

Другие советы

В теория, объявление строки внутри цикла — это пустая трата ресурсов.В упражняться, однако оба представленных вами фрагмента будут скомпилированы в один и тот же код (объявление вне цикла).

Итак, если ваш компилятор выполняет какую-либо оптимизацию, разницы нет.

В общем, я бы выбрал второй, потому что область действия переменной 's' ограничена циклом.Преимущества:

  • Это лучше для программиста, потому что вам не нужно беспокоиться о том, что где-то позже в функции снова будет использоваться 's'.
  • Это лучше для компилятора, поскольку область действия переменной меньше, и поэтому он потенциально может выполнять больший анализ и оптимизацию.
  • Это лучше для будущих читателей, потому что они не будут задаваться вопросом, почему переменная 's' объявлена ​​вне цикла, если она никогда не будет использоваться позже.

Если вы хотите ускорить циклы, я предпочитаю объявлять переменную max рядом со счетчиком, чтобы не требовалось повторных поисков условия:

вместо

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

Любые другие вещи, которые следует учитывать, уже были упомянуты, так что только мои два цента (см. пост Эриксона)

Гретц, Гад

Добавлю немного к @Ответ Эстебана Арайи, они оба потребуют создания новой строки каждый раз в цикле (в качестве возвращаемого значения some Assignment выражение).Эти строки в любом случае должны быть очищены от мусора.

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

Просматривая исходный код 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;
}

Мне кажется, что нам нужна более конкретизация проблемы.

А

s = some Assignment;

Что это за задание, не уточняется.Если задание

s = "" + i + "";

тогда необходимо выделить новое жало.

но если это так

s = some Constant;

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

Кажется, мне немного глупо беспокоиться о значительной оптимизации цикла for для интерпретируемого языка, ИМХО.

Когда я использую несколько потоков (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();
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top