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

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

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

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

Ответ 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" во многих случаях не может быть затронуто вообще!

Связанный

Ответ 2

В 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

Ответ 3

Компилятор оптимизирует n + 1 в ничто.

Вы имеете в виду n = n + 1?

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

Ответ 4

Кто это говорит? Ваш компилятор оптимизирует все это, действительно, делая его спорным.

Ответ 5

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

Ответ 6

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

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

Ответ 10

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