Разница между объявлением переменных до или в цикле? - 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 ]

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

Что лучше, a или b ?

С точки зрения производительности вам придется ее измерять. (И, по моему мнению, если вы можете измерить разницу, компилятор не очень хорош).

С точки зрения обслуживания, b лучше. Объявите и инициализируйте переменные в одном и том же месте, в самой узкой области видимости. Не оставляйте зазор между объявлением и инициализацией и не загрязняйте пространства имен, которые вам не нужны.

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

Ну, я запускал ваши примеры A и B по 20 раз каждый, повторяя 100 миллионов раз. (JVM - 1.5.0)

A: среднее время выполнения: 0,074 с

B: среднее время выполнения: 0,067 с

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

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

Это зависит от языка и точного использования. Например, в C # 1 это не имеет значения. В C # 2, если локальная переменная захвачена анонимным методом (или лямбда-выражением в C # 3), это может иметь очень существенное значение.

Пример:

using System;
using System.Collections.Generic;

class Test
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        int outer;
        for (int i=0; i < 10; i++)
        {
            outer = i;
            int inner = i;
            actions.Add(() => Console.WriteLine("Inner={0}, Outer={1}", inner, outer));
        }

        foreach (Action action in actions)
        {
            action();
        }
    }
}

Выход:

Inner=0, Outer=9
Inner=1, Outer=9
Inner=2, Outer=9
Inner=3, Outer=9
Inner=4, Outer=9
Inner=5, Outer=9
Inner=6, Outer=9
Inner=7, Outer=9
Inner=8, Outer=9
Inner=9, Outer=9

Разница в том, что все действия захватывают одну и ту же переменную outer, но у каждого есть своя отдельная переменная inner.

35 голосов
/ 13 января 2010

Вот что я написал и скомпилировал в .NET.

double r0;
for (int i = 0; i < 1000; i++) {
    r0 = i*i;
    Console.WriteLine(r0);
}

for (int j = 0; j < 1000; j++) {
    double r1 = j*j;
    Console.WriteLine(r1);
}

Это то, что я получаю от .NET Reflector , когда CIL возвращается в код.

for (int i = 0; i < 0x3e8; i++)
{
    double r0 = i * i;
    Console.WriteLine(r0);
}
for (int j = 0; j < 0x3e8; j++)
{
    double r1 = j * j;
    Console.WriteLine(r1);
}

Так что оба выглядят одинаково после компиляции. В управляемых языках код преобразуется в CL / байт-код, а во время исполнения он преобразуется в машинный язык. Таким образом, на машинном языке двойка может даже не быть создана в стеке. Это может быть просто регистр, поскольку код отражает, что это временная переменная для функции WriteLine. Существует целый набор правил оптимизации только для циклов. Так что средний парень не должен беспокоиться об этом, особенно в управляемых языках. Есть случаи, когда вы можете оптимизировать управление кодом, например, если вам нужно объединить большое количество строк, используя только string a; a+=anotherstring[i] против использования StringBuilder. Существует очень большая разница в производительности между ними. Есть много таких случаев, когда компилятор не может оптимизировать ваш код, потому что он не может понять, что задумано в большей области. Но он может в значительной степени оптимизировать основные вещи для вас.

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

Это ошибка в VB.NET. Результат Visual Basic не будет повторно инициализировать переменную в этом примере:

For i as Integer = 1 to 100
    Dim j as Integer
    Console.WriteLine(j)
    j = i
Next

' Output: 0 1 2 3 4...

При первом выводе будет 0 (переменные Visual Basic имеют значения по умолчанию при объявлении!), Но i каждый раз после этого.

Если вы добавите = 0, вы получите то, что ожидаете:

For i as Integer = 1 to 100
    Dim j as Integer = 0
    Console.WriteLine(j)
    j = i
Next

'Output: 0 0 0 0 0...
15 голосов
/ 10 октября 2015

Я сделал простой тест:

int b;
for (int i = 0; i < 10; i++) {
    b = i;
}

против

for (int i = 0; i < 10; i++) {
    int b = i;
}

Я скомпилировал эти коды с помощью gcc - 5.2.0. И тогда я разобрал главное () из этих двух кодов и вот результат:

1º:

   0x00000000004004b6 <+0>:     push   rbp
   0x00000000004004b7 <+1>:     mov    rbp,rsp
   0x00000000004004ba <+4>:     mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret

против

   0x00000000004004b6 <+0>: push   rbp
   0x00000000004004b7 <+1>: mov    rbp,rsp
   0x00000000004004ba <+4>: mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret 

Которые точно так же, как результат. разве это не доказательство того, что два кода производят одно и то же?

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

Это зависит от языка - IIRC C # оптимизирует это, поэтому нет никакой разницы, но JavaScript (например) будет каждый раз выполнять весь процесс выделения памяти.

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

Я бы всегда использовал A (а не полагался на компилятор) и мог бы также переписать:

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

Это все еще ограничивает intermediateResult областью действия цикла, но не повторяет объявление во время каждой итерации.

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

На мой взгляд, b - лучшая структура. В последнем значении middleResult остается после завершения цикла.

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

5 голосов
/ 08 ноября 2012

Существует разница в C #, если вы используете переменную в лямбде и т. Д. Но в целом компилятор будет делать то же самое, предполагая, что переменная используется только внутри цикла.

Учитывая, что они в основном одинаковы: обратите внимание, что версия b делает читателей гораздо более очевидным, что переменная не используется и не может использоваться после цикла. Кроме того, версия b гораздо легче реорганизовать. В версии a сложнее извлечь тело цикла в его собственный метод. Более того, версия b уверяет вас, что такой рефакторинг не имеет побочных эффектов.

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

...