Разница между объявлением переменных до или в цикле? - PullRequest
304 голосов
/ 02 января 2009

Я всегда задавался вопросом, имеет ли какое-либо значение (производительность), вообще говоря, объявление одноразовой переменной перед циклом, в отличие от многократного внутри цикла? Пример (совершенно бессмысленный) на Java:

a) объявление перед циклом:

double intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

b) объявление (многократно) внутри цикла:

for(int i=0; i < 1000; i++){
    double intermediateResult = i;
    System.out.println(intermediateResult);
}

Какой из них лучше, a или b ?

Я подозреваю, что повторное объявление переменных (пример b ) создает дополнительные издержки в теории , но компиляторы достаточно умны, чтобы это не имело значения. Пример b имеет преимущество в том, что он более компактен и ограничивает область действия переменной тем местом, где она используется. Тем не менее, я склонен кодировать в соответствии с примером a .

Редактировать: Меня особенно интересует случай с Java.

Ответы [ 25 ]

5 голосов
/ 02 января 2009

Сотрудник предпочитает первую форму, говоря, что это оптимизация, предпочитая повторно использовать объявление.

Я предпочитаю второй (и пытаюсь убедить моего сотрудника! ;-)), прочитав это:

  • Это уменьшает область видимости переменных там, где они нужны, и это хорошо.
  • Java оптимизирует достаточно, чтобы не иметь существенного различия в производительности. IIRC, возможно, вторая форма еще быстрее.

В любом случае, он относится к категории преждевременной оптимизации, которая зависит от качества компилятора и / или JVM.

5 голосов
/ 02 января 2009

Как правило, я объявляю свои переменные в максимально возможной области видимости. Итак, если вы не используете промежуточный результат вне цикла, то я бы выбрал B.

5 голосов
/ 02 января 2009

Я подозреваю, что несколько компиляторов могут оптимизировать оба кода, чтобы они были одним и тем же кодом, но, конечно, не всем. Так что я бы сказал, что вам лучше с первым. Единственная причина последнего - если вы хотите убедиться, что объявленная переменная используется только в вашем цикле.

5 голосов
/ 23 октября 2011

Ну, вы всегда можете сделать это:

{ //Or if(true) if the language doesn't support making scopes like this
    double intermediateResult;
    for (int i=0; i<1000; i++) {
        intermediateResult = i;
        System.out.println(intermediateResult);
    }
}

Таким образом, вы объявляете переменную только один раз, и она умрет, когда вы выйдете из цикла.

4 голосов
/ 16 января 2010

Я всегда думал, что если вы объявляете свои переменные внутри цикла, то вы тратите впустую память. Если у вас есть что-то вроде этого:

for(;;) {
  Object o = new Object();
}

Тогда не только нужно создавать объект для каждой итерации, но и для каждого объекта должна быть назначена новая ссылка. Похоже, что если сборщик мусора работает медленно, у вас будет куча свисающих ссылок, которые необходимо очистить.

Однако, если у вас есть это:

Object o;
for(;;) {
  o = new Object();
}

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

3 голосов
/ 02 января 2009

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

3 голосов
/ 05 февраля 2014

Моя практика следующая:

  • если тип переменной простой (int, double, ...) Я предпочитаю вариант b (внутри).
    Причина: уменьшение области видимости переменной.

  • если тип переменной не простой (какой-то class или struct) Я предпочитаю вариант a (снаружи).
    Причина: уменьшение количества вызовов ctor-dtor.

1 голос
/ 20 апреля 2015

С точки зрения производительности снаружи (намного) лучше.

public static void outside() {
    double intermediateResult;
    for(int i=0; i < Integer.MAX_VALUE; i++){
        intermediateResult = i;
    }
}

public static void inside() {
    for(int i=0; i < Integer.MAX_VALUE; i++){
        double intermediateResult = i;
    }
}

Я выполнил обе функции по 1 млрд раз каждая. outside () заняло 65 миллисекунд. inside () заняло 1,5 секунды.

0 голосов
/ 09 февраля 2019

У меня был тот же самый вопрос в течение длительного времени. Поэтому я протестировал еще более простой кусок кода.

Вывод: Для таких случаев существует НЕТ разница в производительности.

Корпус внешней петли

int intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i+2;
    System.out.println(intermediateResult);
}

Корпус внутренней петли

for(int i=0; i < 1000; i++){
    int intermediateResult = i+2;
    System.out.println(intermediateResult);
}

Я проверил скомпилированный файл на декомпиляторе IntelliJ и в обоих случаях получил одинаковое Test.class

for(int i = 0; i < 1000; ++i) {
    int intermediateResult = i + 2;
    System.out.println(intermediateResult);
}

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

Корпус внешней петли

Code:
  stack=2, locals=3, args_size=1
     0: iconst_0
     1: istore_2
     2: iload_2
     3: sipush        1000
     6: if_icmpge     26
     9: iload_2
    10: iconst_2
    11: iadd
    12: istore_1
    13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    16: iload_1
    17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    20: iinc          2, 1
    23: goto          2
    26: return
LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13      13     1 intermediateResult   I
            2      24     2     i   I
            0      27     0  args   [Ljava/lang/String;

Корпус внутренней петли

Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: sipush        1000
         6: if_icmpge     26
         9: iload_1
        10: iconst_2
        11: iadd
        12: istore_2
        13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: iload_2
        17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        20: iinc          1, 1
        23: goto          2
        26: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13       7     2 intermediateResult   I
            2      24     1     i   I
            0      27     0  args   [Ljava/lang/String;

Если вы обратите пристальное внимание, то только Slot, присвоенные i и intermediateResult в LocalVariableTable, заменяются как продукт их порядка появления. Такая же разница в слоте отражена в других строках кода.

  • Никаких дополнительных операций не выполняется
  • intermediateResult все еще является локальной переменной в обоих случаях, поэтому нет разницы во времени доступа.

БОНУС

Компиляторы выполняют тонну оптимизации, посмотрите, что происходит в этом случае.

Нулевой рабочий случай

for(int i=0; i < 1000; i++){
    int intermediateResult = i;
    System.out.println(intermediateResult);
}

Ноль работы декомпилированы

for(int i = 0; i < 1000; ++i) {
    System.out.println(i);
}
0 голосов
/ 19 февраля 2018

Попробовал то же самое в Go и сравнил вывод компилятора, используя go tool compile -S с go 1.9.4

Нулевая разница согласно выводу ассемблера.

...