Sign in to follow this  

pre-increment versus post-increment

Recommended Posts

Hi all,

I'm reading Game Engine Architecture and stumbled upon the topic 'pre-' versus 'post-increment' in for loops.

I.e.:

 

++i (pre)

i++ (post)

 

From 'the old days' I remember that I've switched from post to pre, always. At that time is was something about (premature) optimization I recall.

But in the book it says it's better to have post, because 'pre' gives a data dependency, CPU must wait for increment to e completed before it's value can be used in the expression. The book also says thay most compilers will solve it in the background anyway.

 

Just out of curiosity, what do you do?

Share this post


Link to post
Share on other sites
Just out of curiosity, what do you do?

 

Since this is curiosity rather than anything backed by facts. I always go for i++ unless I specifically need the pre-version. For loops I don't think I have ever used ++i. I use the post version simply because it was the first increment operator I saw/learned and it does the job. I did read a post recently mentioning the pre version and also commenting that the benefit of it no longer applies. How much truth is in that I cannot say. Compilers do tend to be very smart now.

Edited by Nanoha

Share this post


Link to post
Share on other sites

How is i++ algorithmically more complex than ++i?

If I am not mistaken, they both lead to the same thing when compiled, right?
They should only be two operations. An "ADD" and a "SET"

Edited by Tangletail

Share this post


Link to post
Share on other sites

Hi all,

I'm reading Game Engine Architecture and stumbled upon the topic 'pre-' versus 'post-increment' in for loops.

I.e.:

 

++i (pre)

i++ (post)

 

From 'the old days' I remember that I've switched from post to pre, always. At that time is was something about (premature) optimization I recall.

But in the book it says it's better to have post, because 'pre' gives a data dependency, CPU must wait for increment to e completed before it's value can be used in the expression. The book also says thay most compilers will solve it in the background anyway.

 

Just out of curiosity, what do you do?

I use pre by default for the reasons Hodgman described.

 

With regards to data dependency, it only applies if the result of the increment is used. So using increment at the end of a for loop does not use the result in an expression so there is no data dependency difference. However, if you are using the result of the operator, the the point is marginally valid. However I still don't find it too compelling for the following reasons: 1)the choice between pre and post increment can effect the readability of the algorithm, so this micro-optimization is less important than those considerations. 2) Rewriting code to avoid the use of preincrement may not actually reduce data dependency because the data dependency can be unavoidable. For example, moving a pre-increment to it's own line and making it a post increment does not reduce data dependency.

 

I'd say that avoiding data dependency is a good idea in designing algorithms in general but using post increment more misses the mark.

Share this post


Link to post
Share on other sites

I use pre because it's algorithmically simpler. In the case of for loops using iterator types instead of integers, for(...; i++) is much more complex than for(...; ++i) (and IMHO - wrong) and often the compiler won't be able to fix that mistake for you, depending on how complex the iterator type is / what your optimization level is.

On the other hand, in the integer for loop case, the compiler is smart enough to switch between i++ and ++i versions in the background for you.
So IMHO, in C/C#/etc, i++ is more typical, but in C++ ++i is the right default.

The difference between the two operations is that post will return the previous value of the variable you increment when assigning to a variable. In the case of user types, like iterators, this can come with a copy cost and as such the rule started to exist that you should prefer per-increment for a loop variable.

When user types are involved the compiler cannot see the optimisations anymore that it can do for POD types, most likely a ++i and i++ will both result in to a inc eax assembly instruction in the case of a for loop. But when its implemented as an overloaded operator the compiler has to insert that code there.

As a simple example this code with a release compile on VS2015:

int i = 0;
int y = ++i;
int x = i++;
//can be a fair amount of code in between here
std::cout << i << x << y;

Will generate this assembly:

00007FF6718C9C43  mov         edx,2  
00007FF6718C9C48  mov         rcx,qword ptr [__imp_std::cout (07FF6718CD1C0h)]  
00007FF6718C9C4F  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF6718CD198h)] 
00007FF6718C9C55  mov         rcx,rax  
00007FF6718C9C58  mov         edx,1  
00007FF6718C9C5D  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF6718CD198h)]  
00007FF6718C9C63  mov         rcx,rax  
00007FF6718C9C66  mov         edx,1  
00007FF6718C9C6B  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (07FF6718CD198h)]

Which shows that for integers the compiler can compile the increment away completely. When you wrap that code in a for loop you get a sub asm call followed by a jne on the counter value to jump back to the beginning of the loop.

Share this post


Link to post
Share on other sites

Depends on your goal. I see very little post increment usages just for the sake of the "post". So in my opionion it's because of conventions in writing. Loops are written with post incremeant, giving us an habit of writing it in that way (for i=0;i<10;i++).

But when you do need to evaluate the value before incremeanting ( var x = i++) So you both save the previous value and incremeant it at the same time, then you use it.

 

From readablity perspective it's nonsense to use that. because the purpose of var x=i++ is to assign i to x, the i++ is a side effect although we've directly written it to do it. It's 2 logical operations inside 1 sentence, which is basicly a smelly code to write (like a function with 2 responsibilities).

 

What was shown above is compiler optimizations. If you provide constant values and do some math with them, it'll simply calculate in compile team.

So (1+2+3+4+5) ++ becomes  16.

It's true with many mathematical operations. The really interesting optimizations are inside the CPU where conditions have to be met.

For example ++i > 5 , How does the cpu know which code to load? The one that ++i > 5 is true or the one that it's smaller? 

if i is known as a const, it will produce the value of 6 , then 6>5 is always true. So it would never load the 'else' code.

But if it's a runtime variable, it would sometimes guess wrong (small cases) and will load both code into the cache. 

Share this post


Link to post
Share on other sites
Just a note: compiler optimization is often disabled in debug mode. Unless you particularly enjoy having your nice interactive game turn into a slideshow when you're trying to fix a bug using a debuggable build, you should get into the habit of the "small" pre-optimizations that result in less work for the compiler and don't require sophisticated optimization passes to make efficient.

Of course, _profile your code_ rather than blindly applying advice off the Internet...

Share this post


Link to post
Share on other sites

IIRC VS elided the integer temporary in for loop in debug last I looked at it. I could look again, but I'm in lazy mode right now. (Also, it won't do that for non-integer types (I hope) so don't rely on it.)

Edited by Khatharr

Share this post


Link to post
Share on other sites

I never use either pre- or post-increment as side effect in a computation, I always write it as a separate statement.

Then immediately, the entire "pre or post"? problem disappears (I always use post, out of habit, and better looks), and the compiler is free to optimize however it likes.

 

I never checked what the compiler actually does there.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this