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

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

или же

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

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

Обновлено: В ответ на ответ Милхауса. Было бы бессмысленно назначать String константе внутри цикла, не так ли? Нет, здесь «какое-то присвоение» означает изменяющееся значение, полученное из списка, по которому выполняется итерация.

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

Нет ничего необычного в итерации по набору строк, заданных как литералы. Например, заголовки столбцов таблицы могут быть жестко заданы как String []. Однако важно то, что в обоих случаях выполняется одно и то же задание, поэтому оно не влияет на ответ.

erickson 21.09.2008 10:33

Еще один комментарий: не забывайте, что если вы не собираетесь изменять значение s, вы должен объявляете его final. Многие программисты Java часто забывают об этом.

Aleksandar Dimitrov 21.09.2008 13:03
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
51
2
8 456
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

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

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

Ссылка просто помещается внутри фрейма стека для вызова этого метода, верно?

Thomas 21.09.2008 07:08

Я думаю, вы неверно истолковали то, что написала 1800 ИНФОРМАЦИЯ. Неизменяемость строк Java здесь не имеет значения: «некоторое присвоение» будет создавать новую строку каждый раз, независимо от того, является ли строка неизменной.

Thomas 21.09.2008 07:30

Обновили Вопрос. Назначение жестко закодированной строки было бы нелогичным.

Vivek Kodira 21.09.2008 09:52

Что это за теория, говорящая вам, что переменная объявление тратит ресурсы впустую?

jrudolph 21.09.2008 17:31

jrudolph: Создание новой пустой строки создает новый объект, собирающий мусор, если я правильно помню свою Java.

TraumaPony 21.09.2008 17:59

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

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

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

Не обязательно. Мы не знаем, что назначается «s» внутри цикла. Возможно, это строковая константа, которая выделяется в пространстве PermGen и никогда не будет собираться сборщиком мусора. Все, что мы знаем, это то, что что бы это ни было, оно одинаково в обоих случаях, поэтому это не имеет значения.

erickson 21.09.2008 10:28

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

1800 INFORMATION 21.09.2008 12:25

Я не имел в виду одну и ту же строковую константу на каждой итерации. См. Мой комментарий к OP.

erickson 21.09.2008 19:44

Да в таком случае оптимизации не было бы

1800 INFORMATION 22.09.2008 00:25

Мне кажется, что нам нужно более детально проработать проблему.

В

s = some Assignment;

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

s = "" + i + "";

затем нужно выделить новое жало.

но если это

s = some Constant;

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

Кажется, мне немного глупо беспокоиться о большой оптимизации цикла for для интерпретируемого языка IMHO.

Ответ принят как подходящий

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

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

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

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

Я все еще не полностью уверен, но я удалил свой ответ, потому что он был неправильным, но для справки по этому вопросу. Вот что у меня было: {// Не снимать фигурные скобки String s; for (i = 0; i <....) {s = некоторое присвоение; }}

Brian R. Bondy 21.09.2008 17:34

Если бы я мог, я бы проголосовал за этот комментарий дважды. Я бы также отметил вопрос «ранняя оптимизация».

Trenton 11.10.2008 10:27

Какой отличный ответ; Я бы также проголосовал за него несколько раз, если бы мог.

Lawrence Dol 01.05.2009 10:25

Я раньше не играл с javap, поэтому проверил цикл for, который получает дату и устанавливает String s равным date.toString. Сделанная мною разборка показала, что код другой. Вы можете видеть, что s устанавливается в каждом цикле внутреннего. Это что-то, что может исправить jit-компилятор?

Philip Tinney 12.10.2010 21:41

@ Филип Т. - Я не уверен, что понимаю, что вы описываете. Если вы имеете в виду, что одно и то же значение присваивалось String на каждой итерации цикла, и спрашиваете, можно ли «поднять» это вычисление, да, это возможно, но это будет зависеть от JVM.

erickson 12.10.2010 21:59

Мой вопрос касался раздела «Объем не влияет на производительность». Поскольку мне было любопытно, я посмотрел на вывод javap класса, реализующего пример с переменной, определенной внутри и вне цикла. Javap показывает, что код другой, с ldc и astore внутри цикла для внутренней инициализированной версии, и этот код находится вне цикла для внешней инициализированной версии. Я пытаюсь выяснить, не упускаю ли я чего-то, поскольку общее мнение, похоже, заключается в том, что код не должен отличаться. Я проверил классы из Eclipse и Netbeans.

Philip Tinney 12.10.2010 22:39

Вы говорите о начальном бесполезном назначении "" на s в первом примере в исходной публикации? Значение, которое никогда не читается, прежде чем оно будет перезаписано на первой итерации цикла? Об этом я и говорю в разделе «Избегайте преждевременной инициализации»; инструкции ldc и astore тратятся впустую, потому что это значение никогда не может быть прочитано до переназначения переменной. Код внутри у цикла такой же. Инициализация (присвоение) переменной - это отдельный вопрос от области действия переменной.

erickson 13.10.2010 00:17

Если вы хотите ускорить циклы, я предпочитаю объявлять переменную 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];
}

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

Greetz, GHad

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

При просмотре исходного кода 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;
}

Вероятно, это было более полезно на старых виртуальных машинах, чем сейчас. Мне трудно поверить, что это имеет значение для HotSpot.

Michael Myers 14.04.2009 00:27

Когда я использую несколько потоков (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();
}

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