В языке C Почему n++
выполняется быстрее, чем n=n+1
?
(int n=...; n++;)
(int n=...; n=n+1;)
Наш инструктор задал этот вопрос в сегодняшнем классе. (это не домашнее задание)
В языке C Почему n++
выполняется быстрее, чем n=n+1
?
(int n=...; n++;)
(int n=...; n=n+1;)
Наш инструктор задал этот вопрос в сегодняшнем классе. (это не домашнее задание)
Это было бы правдой, если вы работаете над компилятором "камень-возраст"...
В случае "каменного века": ++n
быстрее, чем n++
быстрее, чем n=n+1
Обычно машина имеет increment x
, а также add const to x
n++
у вас будет только доступ к памяти (чтение n, inc n, запись n)n=n+1
у вас будет 3 доступа к памяти (прочитайте n, прочитайте const, добавьте n и const, напишите n) Но сегодня компилятор автоматически преобразует n=n+1
в ++n
, и он сделает больше, чем вы можете себе представить!
Также на сегодняшний день процессоры нестандартного порядка - несмотря на то, что во время исполнения компилятора "stone-age" во многих случаях не может быть затронуто вообще!
В GCC 4.4.3 для x86 с оптимизацией или без нее они компилируются в один и тот же код сборки и, таким образом, выполняют одинаковое количество времени для выполнения. Как вы можете видеть в сборке, GCC просто преобразует n++
в n=n+1
, а затем оптимизирует его в однострочном добавлении (в -O2).
Ваше предложение инструктора о том, что n++
быстрее применяется только к очень старым, не оптимизирующим компиляторам, которые недостаточно умен, чтобы выбрать инструкции обновления на месте для n = n + 1
. Эти компиляторы в течение многих лет устарели в мире ПК, но все еще могут быть найдены для странных патентованных встроенных платформ.
C-код:
int n;
void nplusplus() {
n++;
}
void nplusone() {
n = n + 1;
}
Выходная сборка (без оптимизации):
.file "test.c"
.comm n,4,4
.text
.globl nplusplus
.type nplusplus, @function
nplusplus:
pushl %ebp
movl %esp, %ebp
movl n, %eax
addl $1, %eax
movl %eax, n
popl %ebp
ret
.size nplusplus, .-nplusplus
.globl nplusone
.type nplusone, @function
nplusone:
pushl %ebp
movl %esp, %ebp
movl n, %eax
addl $1, %eax
movl %eax, n
popl %ebp
ret
.size nplusone, .-nplusone
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
.section .note.GNU-stack,"",@progbits
Выходная сборка (с оптимизацией -O2):
.file "test.c"
.text
.p2align 4,,15
.globl nplusplus
.type nplusplus, @function
nplusplus:
pushl %ebp
movl %esp, %ebp
addl $1, n
popl %ebp
ret
.size nplusplus, .-nplusplus
.p2align 4,,15
.globl nplusone
.type nplusone, @function
nplusone:
pushl %ebp
movl %esp, %ebp
addl $1, n
popl %ebp
ret
.size nplusone, .-nplusone
.comm n,4,4
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
.section .note.GNU-stack,"",@progbits
Компилятор оптимизирует n + 1
в ничто.
Вы имеете в виду n = n + 1
?
Если это так, они будут скомпилированы для идентичной сборки. (Предполагая, что оптимизация включена и что они являются операторами, а не выражениями)
Кто это говорит? Ваш компилятор оптимизирует все это, действительно, делая его спорным.
Современные компиляторы должны иметь возможность распознавать обе формы как эквивалентные и преобразовывать их в формат, который лучше всего работает на вашей целевой платформе. Существует одно исключение из этого правила: переменные обращения, которые имеют побочные эффекты. Например, если n
- это определенный аппаратный регистр с отображением памяти, считывание из него и запись на него могут сделать больше, чем просто перенос значения данных (например, чтение может очистить прерывание). Вы должны использовать ключевое слово volatile
, чтобы компилятор знал, что он должен быть осторожным в оптимизации доступа к n
, и в этом случае компилятор может генерировать другой код из n++
(операция увеличения) и n = n + 1
( читать, добавлять и хранить операции). Однако для обычных переменных компилятор должен оптимизировать обе формы для одного и того же.
На самом деле это не так. Компилятор внесет изменения, специфичные для целевой архитектуры. Такие микро-оптимизации, как это часто, имеют сомнительные преимущества, но, что важно, безусловно, не стоят времени программиста.
Собственно, причина в том, что оператор определяется по-разному для пост-исправления, чем для предварительного исправления. ++n
увеличит "n" и вернет ссылку на "n", а n++
увеличит "n", вернет копию const
"n". Следовательно, фраза n = n + 1
будет более эффективной. Но я должен согласиться с этими плакатами. Хорошие компиляторы должны оптимизировать неиспользуемый возвращаемый объект.
В языке C побочный эффект выражений n++
по определению эквивалентен побочному эффекту выражения n = n + 1
. Поскольку ваш код основан только на побочных эффектах, сразу становится очевидным, что правильный ответ заключается в том, что это выражение всегда имеет точно эквивалентную производительность. (Независимо от каких-либо параметров оптимизации в компиляторе, BTW, поскольку проблема не имеет абсолютно никакого отношения к оптимизации.)
Любое практическое расхождение в выполнении этих выражений возможно только в том случае, если компилятор намеренно (и злонамеренно!) пытается ввести эту дивергенцию. Но в этом случае он может идти в любом случае, конечно, то есть независимо от того, как автор компилятора хотел его перекосить.
Я думаю, что это больше похоже на аппаратный вопрос, а не на программное обеспечение... Если я правильно помню, в более старых процессорах n = n + 1 требует двух локаций памяти, где ++ n - это просто команда микроконтроллера... Но я сомневаюсь, что это применимо к современным архитектурам...
Все это зависит от директив компилятора/процессора/компиляции. Поэтому делать какие-либо предположения "что быстрее в целом" - не очень хорошая идея.