Почему n++ выполняется быстрее, чем n=n+1?

на языке C, почему n++ выполнить быстрее, чем n=n+1?

(int n=...;  n++;)
(int n=...;  n=n+1;)

наш инструктор задал этот вопрос в сегодняшнем классе. (это не домашнее задание)

10 ответов


это было бы верно, если вы работаете на "каменного века" компилятора...

в случае "каменного века":
++n быстрее n++ быстрее n=n+1
Машина обычно имеет increment x а также add const to x

  • в случае n++, у вас будет только 2 доступа к памяти (чтение n, inc n, запись n )
  • в случае n=n+1, у вас будет 3 доступа к памяти (читать n, читать const, добавить n и const, напишите n)

но сегодняшний компилятор автоматически преобразует n=n+1 to ++n, и это сделает больше, чем вы можете себе представить!!

также на сегодняшних неработающих процессорах-несмотря на случай "каменного века" компилятор - среда выполнения не может быть затронуты на всех во многих случаях!!

по теме


на GCC 4.4.3 для x86, с и без оптимизации, они компилируются в тот же самый код сборки и, таким образом, занимают столько же времени для выполнения. Как вы можете видеть в сборке, GCC просто преобразует n++ на n=n+1, затем оптимизирует его в одну инструкцию add (в-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    , %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    , %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    , n
    popl    %ebp
    ret
    .size   nplusplus, .-nplusplus
    .p2align 4,,15
.globl nplusone
    .type   nplusone, @function
nplusone:
    pushl   %ebp
    movl    %esp, %ebp
    addl    , 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 (операции чтения, добавления и хранения). Однако для нормальных переменных компилятор должен оптимизировать обе формы до одного и того же.


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


на самом деле, причина в том, что оператор определяется по-разному для post-fix, чем для pre-fix. ++n увеличит "n "и вернет ссылку на" n " в то время как n++ приращение "n" будет возвращать const копия "n". Отсюда и фраза n = n + 1 будет более эффективным. Но я должен согласиться с вышеприведенными плакатами. Хорошие компиляторы должны оптимизировать неиспользуемый объект return.


на языке C побочный эффект n++ выражения по определению эквивалент побочного эффекта n = n + 1 выражение. Поскольку ваш код полагается только на побочные эффекты, сразу очевидно, что правильный ответ заключается в том, что эти выражения всегда имеют точно эквивалентную производительность. (Независимо от каких-либо настроек оптимизации в компиляторе, кстати, поскольку проблема не имеет абсолютно никакого отношения к какой-либо оптимизации.)

никакого практического различия в производительность этих выражений возможна только в том случае, если компилятор намеренно (и злонамеренно!) пытается ввести это расхождение. Но в этом случае он может пойти в любом случае, конечно, т. е. в любом случае автор компилятора хотел его исказить.


Я думаю, что это больше похоже на аппаратное, а не программное обеспечение... Если я правильно помню, в старых процессорах n=n+1 требует двух местоположений памяти, где ++n-это просто команда микроконтроллера... Но я сомневаюсь, что это применимо к современной архитектуре...


все это зависит от директив компилятора/процессора/компиляции. Поэтому делать какие-либо предположения "что быстрее вообще" - не очень хорошая идея.