Ответ 1
Пространство стека для локальных переменных обычно выделяется в области функций. Поэтому в цикле не происходит регулировки указателя стека, просто присваивая значение 4 var
. Поэтому эти два фрагмента имеют одинаковые накладные расходы.
Мне просто интересно, будет ли потеря скорости или эффективности, если вы сделали что-то вроде этого:
int i = 0;
while(i < 100)
{
int var = 4;
i++;
}
который объявляет int var
сто раз. Мне кажется, что так будет, но я не уверен. было бы более практично/быстрее сделать это вместо этого:
int i = 0;
int var;
while(i < 100)
{
var = 4;
i++;
}
или они одинаковы, быстрее и эффективнее?
Пространство стека для локальных переменных обычно выделяется в области функций. Поэтому в цикле не происходит регулировки указателя стека, просто присваивая значение 4 var
. Поэтому эти два фрагмента имеют одинаковые накладные расходы.
Для примитивных типов и типов POD это не имеет значения. Компилятор выделит пространство стека для переменной в начале функции и освободит ее, когда функция вернется в обоих случаях.
Для типов классов, отличных от POD, которые имеют нетривиальные конструкторы, это будет иметь значение - в этом случае, если переменная, находящаяся за пределами цикла, вызовет только один конструктор и деструктор, а оператор присваивания - каждую итерацию, тогда как он внутри цикла вызовет конструктор и деструктор для каждой итерации цикла. В зависимости от того, что делает оператор конструктора класса, деструктора и оператора присваивания, это может быть или не быть желательным.
Они оба одинаковы, и вот как вы можете это узнать, посмотрев, что делает компилятор (даже если оптимизация не установлена на высокий уровень):
Посмотрите, что делает компилятор (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
Это примерно так же эффективно, как и вы, без полного удаления цикла.
В наши дни лучше объявить его внутри цикла, если он не является постоянным, поскольку компилятор сможет лучше оптимизировать код (уменьшая область видимости переменной).
EDIT: этот ответ в основном устарел. С появлением постклассических компиляторов случаи, когда компилятор не может понять это, становятся редкими. Я все еще могу их построить, но большинство людей классифицируют конструкцию как плохой код.
Большинство современных компиляторов оптимизируют это для вас. При этом я бы использовал ваш первый пример, поскольку считаю его более читаемым.
Для встроенного типа, вероятно, не будет никакой разницы между двумя стилями (вероятно, вплоть до сгенерированного кода).
Однако, если переменная является классом с нетривиальным конструктором/деструктором, вполне может быть существенное различие в стоимости выполнения. Я обычно рассматривал переменную внутри цикла (чтобы уменьшить область как можно меньше), но если это окажет влияние на меня, я бы посмотрел на перемещение переменной класса за пределы области цикла. Тем не менее, для этого требуется дополнительный анализ, поскольку семантика пути 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...
}
Оба цикла имеют одинаковую эффективность. Они оба будут занимать бесконечное количество времени. Может быть хорошей идеей увеличить я внутри петель.
Я однажды провел некоторые тесты производительности, и, к моему удивлению, обнаружил, что случай 1 был на самом деле быстрее! Полагаю, это может быть потому, что объявление переменной внутри цикла уменьшает ее объем, поэтому он освобождается раньше. Однако это было давно, на очень старом компиляторе. Я уверен, что современные компиляторы делают лучшую работу по оптимизации различий, но все равно не мешает держать область переменной как можно короче.
#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 раз, что означает, что локальная переменная внутри цикла выделяется только один раз за каждый вызов функции.
Единственный способ убедиться в их времени. Но разница, если таковая есть, будет микроскопической, поэтому вам понадобится мощная большая петля синхронизации.
Более того, первый из них лучше, потому что он инициализирует переменную var, в то время как другая оставляет ее неинициализированной. Это и руководство, в соответствии с которым следует определять переменные как можно ближе к их точке использования, означает, что первая форма должна быть предпочтительной.
Только с двумя переменными компилятор, скорее всего, назначит регистр для обоих. Эти регистры существуют в любом случае, поэтому это не требует времени. В обоих случаях есть 2 записи регистров и одна команда чтения регистра.
Я думаю, что в большинстве ответов отсутствует главный момент, чтобы рассмотреть, что есть: "Ясно ли это" и, очевидно, по всему обсуждению факт; нет. Я бы предположил, что в большинстве циклов кода эффективность в значительной степени не проблема (если вы не вычисляете маркерный посадочный модуль), так что действительно единственный вопрос - это то, что выглядит более разумным, читабельным и поддерживаемым - в этом случае я бы рекомендовал объявить переменная спереди и снаружи цикла - это просто делает ее более ясной. Тогда такие люди, как вы и я, даже не потрудились тратить время на проверку онлайн, чтобы узнать, действительно ли это или нет.
thats not true есть накладные расходы, но его пренебрежительные накладные расходы.
Несмотря на то, что, вероятно, они будут в одном месте в стеке, он все равно назначает его. Он назначит ячейку памяти для стека для этого int, а затем освободит ее в конце}. Не в свободном смысле кучи в смысле, он будет перемещать sp (указатель стека) на 1. И в вашем случае, учитывая, что у него есть только одна локальная переменная, он просто просто приравнивает fp (указатель кадра) и sp
Короткий ответ был бы следующим: НЕ ПОМОГАЙТЕ, ЧТО ПУТЬ РАБОТАЕТ ПОЧТИ ЖЕ.
Но попробуйте больше узнать о том, как организован стек. В моей школе с неполным образованием были довольно хорошие лекции по этому Если вы хотите прочитать больше здесь http://www.cs.utk.edu/~plank/plank/classes/cs360/360/notes/Assembler1/lecture.html