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

На языке Си, Почему 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 в ++n и это сделает больше, чем вы можете себе представить!

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

связанные с

В 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 is some memory-mapped hardware register, reading from it and writing to it may do more than just transferring a data value (reading might clear an interrupt, for instance). Вы бы использовали volatile keyword to let the compiler know that it needs to be careful about optimizing accesses to n, and in that case the compiler might generate different code from n++ (increment operation) and n = n + 1 (read, add, and store operations). However for normal variables, the compiler should optimize both forms to the same thing.

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

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

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

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

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

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

Другие вопросы по тегам