1
$\begingroup$

There are several coding operations that (I suspect) most compilers will change into more efficient operations at the assembly or machine code level. I have several questions about whether or not the compiler does any of this work, and if it's even best for the compiler to automatically do so.

(Note: These examples use C and C-like language syntax. However, just because i'm doing examples in C, doesn't mean I only want answers for the C compilers. I'm interested in how most major languages handle these things.)

The first is a += 1; vs a++;, where a is any integer variable. Both these codes ultimately result in the same thing (incrementing by 1), but a++ does it quicker somehow. I was self-taught for my first few languages and just never came across the a++ style, so I use the a += 1 style in all my private programming. However, of course later I discovered that a++ is more common when you only need to increment by 1. I believe the origins of it are from the old days where computers were super slow, therefore finding a shortcut for incrementing (or decrementing) by 1 was a huge deal. Now don't get me wrong, it could still make a difference today like in huge nested loops, and the most common place i see the shortcut is in loops anyway. Is there any reason why you would strictly want the slower a += 1 style? Incidentally, do most compilers automatically translate a a += 1 into a a++?

The second is dividing by a power of 2. b /= 8; can be shortcut to a simple bit shift by 3 bits to the right (towards the least significant bit, and filling in zeros on the MSB side)---as long as b is an integer. Should compilers make this translation, or is there some instance where you want the slower b /= 8 operation? Do compilers typically optimize this?

Can't think of any more simple types of shortcuts, but wouldn't be surprised if some exist.

$\endgroup$
  • $\begingroup$ This question might be off-topic for CS.SE, but might be on-topic for StackOverflow. However: 1) Do not cross-post or re-post on StackOverflow, as that violates site rules. Instead, click "flag" to ask the moderators to migrate it for you. 2) Make sure you search on StackOverflow to make sure it hasn't been asked before, and do research to check whether this has been answered elsewhere. You might want to edit the question to show your research. Users of StackOverflow often want you to do research on your own before asking, and want to see in the question what research you've done . $\endgroup$ – D.W. Oct 2 '14 at 0:31
  • 1
    $\begingroup$ You're asking why you might want to take longer to do something identical? Have you considered a career in consulting? $\endgroup$ – corsiKa Oct 2 '14 at 3:14
  • $\begingroup$ a++ is (also) an expression while a+=1 is (only) a statement, so they are not equivalent on the language level. I assume you are asking about what happens when both are used as statement? $\endgroup$ – Raphael Oct 2 '14 at 5:26
  • $\begingroup$ Furthermore, I think the answer depends mostly on "how many cases does your grammar distinguish" and "what is the instruction set". For instance, do you have ALU operations that take constants directly, or do you always have to load constants with an extra operation? $\endgroup$ – Raphael Oct 2 '14 at 5:32
  • 1
    $\begingroup$ @D.W. It would be difficult to extract something that Stack Overflow would accept from this question. The part about compiler design is borderline there. The part about listing the behavior of existing compilers would be considered too broad, just like it is here. General advice about compiler design is on-topic here. $\endgroup$ – Gilles Oct 2 '14 at 9:53
4
$\begingroup$

First compilers may (and often will depending on the optimization level) do transformations far more complex than the one you describe. You should not be concerned about the performance implication of the way you express the increment or the division before being sure that you are using a compiler for which it matters.

But there are some other implications. Some are just stylistic (and here my recommendation is that you just follow what is the style of your organization, if you are in position to chose the style, chose one which suit you and don't second guess yourself about it every other week). Other are semantic.

a += 1 and a++ are equivalent only if the context allows. In C and C++, they are both expressions, and if the result of the expression is different (a += 1 is the incremented value; a++ is the non incremented value, ++a returns the incremented value).

a/8 and a>>3 are equivalent only if the value of a is positive or null. For negative values, C and C++ the result is not determined by the C and C++ standards but left to the implementation(*). The most common behavior gives the same result as a/8 for exact multiple of 8. For negative values which are not a multiple of 8, a/8 has a result truncated towards 0 (i.e. -1/8 is 0) while a>>3 is truncated towards minus infinity (i.e. -1 >> 3 is -1). You may know that the replacement is valid for your program but that is hard to determine for the compiler which will often not do the replacement or use a more complex one.


(*) Two reasons: the standards are defined in such a way that machines with ones' complement or sign and magnitude representation for signed values is possible and what is the natural result depend on the representation; some uncommon machines have only an unsigned shift.

$\endgroup$
3
$\begingroup$

In many languages there is no1 performance difference between the forms. a++, ++a and a += 1 are simply all syntactic sugar for a = a + 1 (similarly with other assignment operators that encode an operator and a basic assignment together). They just provide the programmer a shorter way of writing the same thing.

Sticking to addition, Java offers four ways of writing a (simple) increment:

int a = 0;
a++;

int b = 0;
b += 1;

int c = 0;
c = c + 1;

int d = 0;
++d;

This gets compiled into the following bytecode (up to the variable numberings changing etc):

0: iconst_0      
1: istore_1      
2: iinc          1, 1
5: iconst_0      
6: istore_2      
7: iinc          2, 1
10: iconst_0      
11: istore_3      
12: iinc          3, 1
15: iconst_0      
16: istore        4
18: iinc          4, 1

You can see that all four versions get translated into three steps; the constant zero is loaded, store in variable $i$ (the names are not visible in this snippet, they just get a number indexing into a table), then variable $i$ is incremented by $1$. The first two correspond to the declaration and initialisation, the third is the increment. The translation of all four different increments is identical at the bytecode level.

For the division example, Java similarly unpacks v /= 8 to v = v / 8, and they produce the same bytecode2.

Footnotes

  1. In all languages that I have seen that support these forms, there is a slight, but important difference between ++a and the three others, but it does not occur in the translation of the increment, but in when the increment happens. If we have a function and pass the increment as an argument foo(++a), foo(a+=1) or foor(a=a+1), this will increment a before invoking the function and pass the result of the increment to the function. With the call foo(a++), a is incremented before the function call, but the value of the argument to the function is the old value of a.

  2. Interestingly with no optimisation in the bytecode, it literally just divides by 8. There may be further optimisation once it hits the JVM though.

$\endgroup$
2
$\begingroup$

First of all, regarding ++. For basic data types, ++a is a shortcut for a+=1, so I expect the resulting code to be the same. Indeed, gcc generates the same code for both. In -O1 the increment instruction is used, while in -O0 the code adds 1. This implies that ++a is converted internally to a+=1, and then during code generation this is optimized to an increment instruction.

The operator a++ is different since it returns the old value a rather than the new value a+1, and if the value is actually used, might require another register.

If a is an object then ++ and + could be overloaded independently, so ++a and a=a+1 are not necessarily equivalent, and the compiler won't translate between them.

Regarding division of an integer by a constant power of 2, this is a common optimization. A quick check reveals that gcc performs it even in -O1. In -O0 the resulting codes are different although the division is still implemented by a shift.

$\endgroup$
  • 1
    $\begingroup$ If you want to experiment yourself, you can use this handy site: gcc.godbolt.org. $\endgroup$ – Yuval Filmus Oct 2 '14 at 1:15
  • $\begingroup$ sorry i should have specified that my examples were using c-like language/syntax. I edited the OP to reflect this. I don't know what -O1 and -O0 mean. Also, I've seen ++a but forget what the exact difference is between that and a++. $\endgroup$ – DrZ214 Oct 2 '14 at 2:23
  • $\begingroup$ -O0 and -O1 are optimization options (there are three main levels, lower means less). If you're interested in optimization, perhaps you should look them up. It's also a good idea to look up the difference between a++ (whose value is a) and ++a (whose value is a+1). $\endgroup$ – Yuval Filmus Oct 2 '14 at 2:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.