views:

1079

answers:

8

In C language, Why does n++ execute faster than n=n+1?

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

Our instructor asked that question in today's class. (this is not homework)

+5  A: 

Who says it does? Your compiler optimizes it all away, really, making it a moot point.

Mahmoud Al-Qudsi
+10  A: 

The compiler will optimize n + 1 into nothingness.

Do you mean n = n + 1?

If so, they will compile to identical assembly. (Assuming that optimizations are on and that they're statements, not expressions)

SLaks
+45  A: 

That would be true if you are working on a "stone-age" compiler...

In case of "stone-age":
++n is faster than n++ is faster than n=n+1
Machine usually have increment x as well as add const to x

  • In case of n++, you will have 2 memory access only (read n, inc n, write n )
  • In case of n=n+1, you will have 3 memory access (read n, read const, add n and const, write n)

But today's compiler will automatically convert n=n+1 to ++n, and it will do more than you may imagine!!

Also on today's out-of-order processors -despite the case of "stone-age" compiler- runtime may not be affected at all in many cases!!

Related

Betamoo
*Why* would it be try on a "stone-age" compiler?
FrustratedWithFormsDesigner
Because it's not smart enough to automatically convert `n=n+1` to `n++`; it would do the whole load, operate, store business as if `1` were any other constant value.
Carl Manaster
No, it is not always machine instruction difference, but it is always memory access one!
Betamoo
@gcc: No, you should NOT say that. You should say "There should be no difference at all between the two. Optimizing compilers have handled things like this for decades, since at least the Bliss-11 compiler described in "Design of an Optimizing Compiler", by Wulf et al."
John R. Strohm
On the computer I learned about this on, `++n` and `n++` were exactly the same speed, it didn't have to do load-increment-store, and the only speed difference you'd see depended on whether `n` was stored in a register or memory (not whether n happened to be in a register right now, but whether or not its allocated storage was in a register)e.g. `move (n)+,r0` moves n to register zero, incrementing n after the move.(VAX-11 assembly)
Stephen P
+1 for actually answering the question in a useful way as opposed to just saying that they're the same, which is useful to know but not the information the OP probably wanted.
quixoto
+1  A: 

It doesn't really. The compiler will make changes specific to the target architecture. Micro-optimizations like this often have dubious benefits, but importantly, are certainly not worth the programmer's time.

DeadMG
+17  A: 

On GCC 4.4.3 for x86, with or without optimizations, they compile to the exact same assembly code, and thus take the same amount of time to execute. As you can see in the assembly, GCC simply converts n++ into n=n+1, then optimizes it into the one-instruction add (in the -O2).

Your instructor's suggestion that n++ is faster only applies to very old, non-optimizing compilers, which were not smart enough to select the in-place update instructions for n = n + 1. These compilers have been obsolete in the PC world for years, but may still be found for weird proprietary embedded platforms.

C code:

int n;

void nplusplus() {
    n++;
}

void nplusone() {
    n = n + 1;
}

Output assembly (no optimizations):

    .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

Output assembly (with -O2 optimizations):

    .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
bdonlan
A: 

Actually, the reason is that the operator is defined differently for post-fix than it is for pre-fix. ++n will increment "n" and return a reference to "n" while n++ will increment "n" will returning a const copy of "n". Hence, the phrase n = n + 1 will be more efficient. But I have to agree with the above posters. Good compilers should optimize away an unused return object.

wheaties
-1 for C++ answer to C question.
R..
@R It was originally marked C/C++.
wheaties
@R and more to the point looking at the history it was edited an astounding 13 times complete with title change, tag changes, and question changes. To mark me down while not taking the historical context is quite poor. Normally I wouldn't be so uppity but does this mean I need to go through all my answers to make sure that the question hasn't been edited to the point where I could get downvoted several months later?
wheaties
A: 

In C language the side-effect of n++ expressions is by definition equivalent to the side effect of n = n + 1 expression. Since your code relies on the side-effects only, it is immediately obvious that the correct answer is that these expression always have exactly equivalent performance. (Regardless of any optimization settings in the compiler, BTW, since the issue has absolutely nothing to do with any optimizations.)

Any practical divergence in performance of these expressions is only possible if the compiler is intentionally (and maliciously!) trying to introduce that divergence. But in this case it can go either way, of course, i.e. whichever way the compiler's author wanted to skew it.

AndreyT
-1: old compilers used to emit different instructions for the different statements because they weren't advanced enough at optimization...
RCIX
@RCIX: What you are saying is absolute nonsense, based on a ridiculous urban legend, according to which the compilers somehow translate C code into machine code in some *literal* fashion. In reality, if the defined semantics of two expressions is the same, the code will be the same. It has absolutely nothing to do with optimization. If your compiler produced different code in this case, it simply meant that your compiler was broken, not that it wasn't "advanced enough at optimization".
AndreyT
No, many simple compilers really do a literal translation, such as always using an ADD instruction for the "+" operator, even in cases where INC would be better. For instance, it used to be common to manually use ">>" to divide by a power of two, because the compiler would use a very slow DIV instruction if you used the "/" operator.
Daniel Newby
@Daniel Newby: That would mean a naive non-production grade compiler. I.e. a compiler written by people who did not understand what they were doing. People, who implemented their own misconceptions about C language instead of the actual specification of C language. Why would someone use such a "compiler" is beyond me. In reality there's no such thing as "literal translation" in C. Nothing in the language specification implies that `+` should be implemented as ADD and that `++` should be implemented by INC.
AndreyT
@AndreyT: No, it just means a simple compiler, which is still common for microcontrollers and oddball systems. There are entire compiler companies for niche platforms that have fewer employees than the Visual C++ optimization team, and their products are quite useful, although the machine code efficiency leaves something to be desired. Moreover C was intentionally designed so that operators map directly to common machine instructions, so simple direct translation would produce a complete compiler.
Daniel Newby
+2  A: 

Modern compilers should be able to recognize both forms as equivalent and convert them to the format that works best on your target platform. There is one exception to this rule: variable accesses that have side effects. For example, if 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). You would use the 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.

bta