На языке C, Почему делает n++
выполнитесь быстрее, чем n=n+1
?
(int n=...; n++;)
(int n=...; n=n+1;)
Наш преподаватель задал тот вопрос в сегодняшнем классе. (это не домашняя работа),
Это будет верно, если вы работаете над компилятор "каменного века" ...
В случае "каменный век" :
++ n
быстрее, чем n ++
быстрее, чем n = n + 1
Машина обычно имеет приращение x
, а также add const к x
n ++
вы будете иметь только 2 доступа к памяти (чтение n, inc n, запись n) n = n + 1
у вас будет 3 доступа к памяти (чтение n, чтение const, добавление n и const , напишите n) Но сегодняшний компилятор автоматически преобразует n = n + 1
в ++ n
, и он сделает больше, чем вы можете себе представить !!
Также на сегодняшних вышедших из строя процессорах - несмотря на случай "каменного века" компилятора - во многих случаях время выполнения не может быть затронуто вообще !!
Компилятор превратит n + 1
в ничто.
Вы имеете в виду n = n + 1
?
Если да, они будут компилироваться в идентичную сборку. (Предполагается, что оптимизация включена и что это операторы, а не выражения)
На самом деле нет. Компилятор будет вносить изменения, специфичные для целевой архитектуры. Подобные микрооптимизации часто имеют сомнительные преимущества, но, что важно, они точно не стоят времени программиста.
Фактически, причина в том, что оператор определяется иначе для пост-исправления, чем для предварительного исправления. ++ n
увеличит «n» и вернет ссылку на «n», в то время как n ++
увеличит «n», вернет копию const
числа «n» . Следовательно, фраза n = n + 1
будет более эффективной. Но я должен согласиться с приведенными выше плакатами. Хорошие компиляторы должны оптимизировать неиспользуемый возвращаемый объект.
В языке Си побочный эффект выражения n++
по определению эквивалентен побочному эффекту выражения n = n + 1
. Поскольку ваш код полагается только на побочные эффекты, сразу становится очевидным, что правильный ответ состоит в том, что эти выражения всегда имеют абсолютно эквивалентную производительность. (Независимо от любых настроек оптимизации в компиляторе, BTW, поскольку этот вопрос не имеет абсолютно никакого отношения ни к каким оптимизациям.)
Любое практическое расхождение в производительности этих выражений возможно только если компилятор намеренно (и злонамеренно!) пытается ввести это расхождение. Но в данном случае оно, конечно, может пойти в любую сторону, т.е. в ту, в какую захочет автор компилятора.
Современные компиляторы должны уметь распознавать обе формы как эквивалентные и преобразовывать их в формат, который лучше всего работает на вашей целевой платформе. Есть одно исключение из этого правила: доступ к переменным, имеющий побочные эффекты. Например, если n
- это некоторый отображенный в память аппаратный регистр, чтение из него и запись в него могут делать больше, чем просто перенос значения данных (например, чтение может очистить прерывание). Вы могли бы использовать ключевое слово volatile
, чтобы сообщить компилятору, что ему нужно быть осторожным при оптимизации доступа к n
, и в этом случае компилятор может сгенерировать код, отличный от n ++
(операция увеличения) и n = n + 1
(операции чтения, добавления и сохранения). Однако для обычных переменных компилятор должен оптимизировать обе формы для одного и того же.
Кто сказал, что он есть? На самом деле ваш компилятор оптимизирует все это, что делает его спорным.
На GCC 4.4.3 для x86, с или без оптимизаций, они компилируются в совершенно одинаковый ассемблерный код, и поэтому занимают одинаковое количество времени на выполнение. Как вы можете видеть в ассемблере, GCC просто преобразует n++
в n=n+1
, а затем оптимизирует его в одноинструкционное сложение (в -O2).
Предположение вашего преподавателя, что n++
быстрее, применимо только к очень старым, неоптимизирующим компиляторам, которые не были достаточно умны, чтобы выбрать инструкции обновления на месте для n = n + 1
. Такие компиляторы уже давно устарели в мире ПК, но их все еще можно найти для странных проприетарных встроенных платформ.
Си код:
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