Есть ли накладные расходы для объявления переменной в цикле? (С++)

Мне просто интересно, будет ли потеря скорости или эффективности, если вы сделали что-то вроде этого:

int i = 0;
while(i < 100)
{
    int var = 4;
    i++;
}

который объявляет int var сто раз. Мне кажется, что так будет, но я не уверен. было бы более практично/быстрее сделать это вместо этого:

int i = 0;
int var;
while(i < 100)
{
    var = 4;
    i++;
}

или они одинаковы, быстрее и эффективнее?

Ответ 1

Пространство стека для локальных переменных обычно выделяется в области функций. Поэтому в цикле не происходит регулировки указателя стека, просто присваивая значение 4 var. Поэтому эти два фрагмента имеют одинаковые накладные расходы.

Ответ 2

Для примитивных типов и типов POD это не имеет значения. Компилятор выделит пространство стека для переменной в начале функции и освободит ее, когда функция вернется в обоих случаях.

Для типов классов, отличных от POD, которые имеют нетривиальные конструкторы, это будет иметь значение - в этом случае, если переменная, находящаяся за пределами цикла, вызовет только один конструктор и деструктор, а оператор присваивания - каждую итерацию, тогда как он внутри цикла вызовет конструктор и деструктор для каждой итерации цикла. В зависимости от того, что делает оператор конструктора класса, деструктора и оператора присваивания, это может быть или не быть желательным.

Ответ 3

Они оба одинаковы, и вот как вы можете это узнать, посмотрев, что делает компилятор (даже если оптимизация не установлена ​​на высокий уровень):

Посмотрите, что делает компилятор (gcc 4.0) на ваши простые примеры:

1.c:

main(){ int var; while(int i < 100) { var = 4; } }

gcc -S 1.c

1.S:

_main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    $0, -16(%ebp)
    jmp L2
L3:
    movl    $4, -12(%ebp)
L2:
    cmpl    $99, -16(%ebp)
    jle L3
    leave
    ret

2.c

main() { while(int i < 100) { int var = 4; } }

gcc -S 2.c

2.s:

_main:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        movl    $0, -16(%ebp)
        jmp     L2
L3:
        movl    $4, -12(%ebp)
L2:
        cmpl    $99, -16(%ebp)
        jle     L3
        leave
        ret

Из них вы можете видеть две вещи: во-первых, код тот же в обоих.

Во-вторых, хранилище для var выделяется вне цикла:

         subl    $24, %esp

И, наконец, единственное, что в цикле - это проверка присвоений и условий:

L3:
        movl    $4, -12(%ebp)
L2:
        cmpl    $99, -16(%ebp)
        jle     L3

Это примерно так же эффективно, как и вы, без полного удаления цикла.

Ответ 4

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

EDIT: этот ответ в основном устарел. С появлением постклассических компиляторов случаи, когда компилятор не может понять это, становятся редкими. Я все еще могу их построить, но большинство людей классифицируют конструкцию как плохой код.

Ответ 5

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

Ответ 6

Для встроенного типа, вероятно, не будет никакой разницы между двумя стилями (вероятно, вплоть до сгенерированного кода).

Однако, если переменная является классом с нетривиальным конструктором/деструктором, вполне может быть существенное различие в стоимости выполнения. Я обычно рассматривал переменную внутри цикла (чтобы уменьшить область как можно меньше), но если это окажет влияние на меня, я бы посмотрел на перемещение переменной класса за пределы области цикла. Тем не менее, для этого требуется дополнительный анализ, поскольку семантика пути OOD может измениться, поэтому это можно сделать только в том случае, если разрешает его использование.

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

Предположим, что у вас есть класс LockMgr, который получает критический раздел при его создании и освобождает его при уничтожении:

while (i< 100) {
    LockMgr lock( myCriticalSection); // acquires a critical section at start of
                                      //    each loop iteration

    // do stuff...

}   // critical section is released at end of each loop iteration

сильно отличается от:

LockMgr lock( myCriticalSection);
while (i< 100) {

    // do stuff...

}

Ответ 7

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

Ответ 8

Я однажды провел некоторые тесты производительности, и, к моему удивлению, обнаружил, что случай 1 был на самом деле быстрее! Полагаю, это может быть потому, что объявление переменной внутри цикла уменьшает ее объем, поэтому он освобождается раньше. Однако это было давно, на очень старом компиляторе. Я уверен, что современные компиляторы делают лучшую работу по оптимизации различий, но все равно не мешает держать область переменной как можно короче.

Ответ 9

#include <stdio.h>
int main()
{
    for(int i = 0; i < 10; i++)
    {
        int test;
        if(i == 0)
            test = 100;
        printf("%d\n", test);
    }
}

Код выше всегда печатает 100 10 раз, что означает, что локальная переменная внутри цикла выделяется только один раз за каждый вызов функции.

Ответ 10

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

Более того, первый из них лучше, потому что он инициализирует переменную var, в то время как другая оставляет ее неинициализированной. Это и руководство, в соответствии с которым следует определять переменные как можно ближе к их точке использования, означает, что первая форма должна быть предпочтительной.

Ответ 11

Только с двумя переменными компилятор, скорее всего, назначит регистр для обоих. Эти регистры существуют в любом случае, поэтому это не требует времени. В обоих случаях есть 2 записи регистров и одна команда чтения регистра.

Ответ 12

Я думаю, что в большинстве ответов отсутствует главный момент, чтобы рассмотреть, что есть: "Ясно ли это" и, очевидно, по всему обсуждению факт; нет. Я бы предположил, что в большинстве циклов кода эффективность в значительной степени не проблема (если вы не вычисляете маркерный посадочный модуль), так что действительно единственный вопрос - это то, что выглядит более разумным, читабельным и поддерживаемым - в этом случае я бы рекомендовал объявить переменная спереди и снаружи цикла - это просто делает ее более ясной. Тогда такие люди, как вы и я, даже не потрудились тратить время на проверку онлайн, чтобы узнать, действительно ли это или нет.

Ответ 13

thats not true есть накладные расходы, но его пренебрежительные накладные расходы.

Несмотря на то, что, вероятно, они будут в одном месте в стеке, он все равно назначает его. Он назначит ячейку памяти для стека для этого int, а затем освободит ее в конце}. Не в свободном смысле кучи в смысле, он будет перемещать sp (указатель стека) на 1. И в вашем случае, учитывая, что у него есть только одна локальная переменная, он просто просто приравнивает fp (указатель кадра) и sp

Короткий ответ был бы следующим: НЕ ПОМОГАЙТЕ, ЧТО ПУТЬ РАБОТАЕТ ПОЧТИ ЖЕ.

Но попробуйте больше узнать о том, как организован стек. В моей школе с неполным образованием были довольно хорошие лекции по этому Если вы хотите прочитать больше здесь http://www.cs.utk.edu/~plank/plank/classes/cs360/360/notes/Assembler1/lecture.html