19

Will the code

int a = ((1 + 2) + 3); // Easy to read

run slower than

int a = 1 + 2 + 3; // (Barely) Not quite so easy to read

or are modern compilers clever enough to remove/optimize "useless" parentheses.

It might seems like a very tiny optimization concern, but choosing C++ over C#/Java/... is all about optimizations (IMHO).

Serge
  • 861
  • 2
  • 6
  • 12
  • 9
    I think C# and Java will optimize that too. I believe when they parse and create AST, they will just remove obvious useless stuff like that. – Farid Nouri Neshat May 12 '14 at 10:29
  • 2
    [Sharing your research helps everyone](http://meta.programmers.stackexchange.com/questions/6559/why-is-research-important). Tell us what you've tried and why it didn’t meet your needs. This demonstrates that you’ve taken the time to try to help yourself, it saves us from reiterating obvious answers, and most of all it helps you get a more specific and relevant answer. Also see [ask] – gnat May 12 '14 at 11:14
  • 5
    Everything I've read points to JIT compilation easily giving ahead-of-time compilation a run for its money, so that alone isn't a very compelling argument. You bring up game programming - the real reason to favor ahead-of-time compilation is that it's predictable - with JIT compilation you never know when the compiler is going to kick in and try to start compiling code. But I would note that ahead-of-time compilation to native code isn't mutually exclusive with garbage collection, see e.g. Standard ML and D. And I've seen convincing arguments that garbage collection is more efficient... – Doval May 12 '14 at 11:52
  • 7
    ...than RAII and smart pointers, so it's more of a question of following the well-beaten path (C++) vs the relatively untread path of doing game programming in those languages. I would also note that worrying about parentheses is insane - I see where you're coming from, but that's a ridiculous micro-optimization. The choice of data structures and algorithms in your program will definitely dominate performance, not such trivialities. – Doval May 12 '14 at 11:53
  • 2
    @Doval none of this is about C# v C++, but the issue at hand is optimisation of the code. JIT doesn't give you nearly as much optimisation opportunities as a slow compile stage does. Some big initialisations can be optimised away entirely by AOT compiles which *could* be a factor even though the example given is a ridiculous triviality. – gbjbaanb May 12 '14 at 12:10
  • @gbjbaanb I'm not an expert on the subject but I don't see how JIT has less opportunities considering you can still do static optimizations when you first parse the code and then you get further optimizations at runtime. A JIT compiler can make assumptions that aren't safe and if it turns out to be wrong can recompile the code. AOT compilation can't make an unsafe assumptions like that. This is why JITs for dynamic languages give big speed-ups - although the `x` in `foo(x)` could theoretically be of any type, if it's always used as an `int`, the JIT can make that optimization at runtime. – Doval May 12 '14 at 12:21
  • 3
    Its about time - a JIT has hardly any time to optimise the bytecode. The bytecode compile stage could perform optimisations, but they just don't - leaving it to the JIT instead. If you spend too long recompiling and optimising at the JIT stage, your app will run very slowly indeed. There are SO questions you can search for more detail. – gbjbaanb May 12 '14 at 12:25
  • @gbjbaanb but JIT gets the oportunity to re-optimize later when needed – ratchet freak May 12 '14 at 12:40
  • 1
    @ratchetfreak but again, JIT may re-optimise but it still has no time to do any optimisations. Any time spent optimising bytecode is time taken away from running the program. You can't run code that hasn't been generated. E.G. If your JIT took a whole minute to optimise your code, your program would hang for a whole minute. Imagine this happening everytime the JIT was invoked. – gbjbaanb May 12 '14 at 12:43
  • 1
    @gbjbaanb you assume the runtime has no way to run code that hasn't gone through JIT, there is always the bytecode interpreter that will run the code before JIT gets the chance, yeah that is slow which is why JIT will kick in to optimize hotspots – ratchet freak May 12 '14 at 12:45
  • 1
    @gbjbaanb Depends very much on the JIT we're talking about. HotSpot c2 does take its time when compiling code (and compiling a few hotspot methods doesn't take minutes and can be done in a background thread) and usually generates similar code to gcc -O3 if we take into account that Java the language disallows many things that gcc can and will do. Biggest thing for C++ is strict control over memory layout and to a lesser degree compiler intrinsics. – Voo May 12 '14 at 12:48
  • 2
    @ratchetfreak ... and the bytecode interpreter will not optimise anything. The original point was that C++ is not as fast as C#... yet now we've got to the conclusion that C# is running interpreted except for un-optimised code because it takes too long to optimise it during runtime :-) (which is roughly true as it happens) – gbjbaanb May 12 '14 at 12:50
  • @gbjbaanb a few things you are ignoring: JIT doesn't run on the application thread, it will run in a background thread; this means that it can take its time optimizing it all; interpreter only runs until JIT has compiled the method (during startup); optimizations available for JIT are at least the same as those for AOT, JIT can see the usage of the method and optimize for the specific case instead of only the generic that AOT is limited to – ratchet freak May 12 '14 at 12:57
  • 1
    @ratchetfreak and things you are ignoring - running code on a background thread is not free (not unless you're running a single threaded app and have CPU to spare, server code will not agree that you can tie up a CPU for as long as you like). The JIT optimisations that are done in all JITs that I know of are relatively cheap ones that are fast to perform. You also ignore that you cannot run code you havn't yet generated and although the bytecode could be interpreted while waiting for the JIT, this means your app will run very slowly. – gbjbaanb May 12 '14 at 13:03
  • 2
    Time to take the JIT discussion away [here](http://stackoverflow.com/questions/5589409/c-sharp-jit-compiling-and-net) or [here](http://stackoverflow.com/questions/7288428/is-there-a-way-to-get-the-net-jit-or-c-sharp-compiler-to-optimize-away-empty-fo?rq=1) – gbjbaanb May 12 '14 at 13:04
  • 6
    Um... What kind of *optimization* are you expecting, exactly? If you're talking about static analysis, in most languages I know of, this will be replaced by the statically known result (LLVM-based implementations even enforce this, AFAIK). If you're talking about execution order, it doesn't matter, as it's the same operation and without side effects. Addition needs two operands anyway. And if you are using this to compare C++, Java and C# regarding performance, it sounds like you don't have a clear idea of what optimizations are and how they work, so you should focus on learning that instead. – Theodoros Chatzigiannakis May 12 '14 at 13:10
  • @gbjbaanb HotSpot c2 (heck even c1) will definitely remove the empty loop you have there. The MS JIT is generally weaker than HotSpot, but I don't believe the claim there either (except if you mean OSR, no idea if the CLR Jit does that). Can you name one particular optimization step that gcc -O3 does that HotSpot c2 doesn't do? (sure both compilers won't do exactly the same optimizations, gcc and icc don't either - I'm talking higher level things like "It doesn't do CSE") Apart from things that a Java compiler isn't allowed to do to begin with obviously. – Voo May 12 '14 at 20:06
  • 5
    I wonder why **a)** you consider the parenthesised expression more readable (to me it just looks ugly, misleading (why do they emphasize this particular order? Shouldn't it be assiciative here?) and clunky) **b)** why you'd think without parentheses it might perform better (clearly parsing parens is _easier_ for a machine than having to reason about operator fixities. As Marc van Leuwen says though, this totally has no influence on runtime). – leftaroundabout May 12 '14 at 20:55
  • 1
    In the specific case of this question, the compiler will remove the parentheses. It will do so because it will recognise that the statement resolves to a constant, which will be inlined. – jwenting May 14 '14 at 07:04
  • Regarding efficiency, picking C++ over C#/Java is all about inlined functions, compile-time polymorphism, and metaprogramming. Low level optimization is something you can do *in addition* to that, but in my opinion it's not the main draw. –  May 14 '14 at 14:50
  • 1
    Floating point maths, like `double b = a * a * a * a * a * a;` won't be optimized into `double b = (a * a * a) * (a * a * a);` by default since their IEEE floating point representation aren't exactly the same. The former do twice more multiplication than the later so the later will be faster. – Alvin Wong May 15 '14 at 07:22
  • 1
    "choosing C++ over C#/Java/... is all about optimizations" - it's sometimes about low-level control, sometimes about outside restrictions (existing code base), sometimes about deterministic execution, existing APIs and so on. Optimizations are mostly something you apply on algorithms (similar in any language), not something you apply because of the language. – utnapistim May 21 '14 at 10:37

11 Answers11

88

The compiler does not actually ever insert or remove parentheses; it just creates a parse tree (in which no parentheses are present) corresponding to your expression, and in doing so it must respect the parentheses you wrote. If you fully parenthesise your expression then it will also be immediately clear to the human reader what that parse tree is; if you go to the extreme of putting in blatantly redundant parentheses as in int a = (((0))); then you will be causing some useless stress on the neurons of the reader while also wasting some cycles in the parser, without however changing the resulting parse tree (and therefore the generated code) the slightest bit.

If you don't write any parentheses, then the parser must still do its job of creating a parse tree, and the rules for operator precedence and associativity tell it exactly which parse tree it must construct. You might consider those rules as telling the compiler which (implicit) parentheses it should insert into your code, although the parser doesn't actually ever deal with parentheses in this case: it just has been constructed to produce the same parse tree as if parentheses were present in certain places. If you place parentheses in exactly those places, as in int a = (1+2)+3; (associativity of + is to the left) then the parser will arrive at the same result by a slightly different route. If you put in different parentheses as in int a = 1+(2+3); then you are forcing a different parse tree, which will possibly cause different code to be generated (though maybe not, as the compiler may apply transformations after building the parse tree, as long as the effect of executing the resulting code would never be different for it). Supposing there is a difference in the resuting code, nothing in general can be said as to which is more efficient; the most important point is of course that most of the time the parse trees do not give mathematically equivalent expressions, so comparing their execution speed is beside the point: one should just write the expression the gives the proper result.

So the upshot is: use parentheses as needed for correctness, and as desired for readability; if redundant they have no effect at all on execution speed (and a negligible effect on compile time).

And none of this has anything to do with optimisation, which comes along way after the parse tree has been built, so it cannot know how the parse tree was constructed. This applies without change from the oldest and stupidest of compilers to the smartest and most modern ones. Only in an interpreted language (where "compile time" and "execution time" are coincident) could there possibly be a penalty for redundant parentheses, but even then I think most such languages are organised so that at least the parsing phase is done only once for each statement (storing some pre-parsed form of it for execution).

Marc van Leeuwen
  • 1,025
  • 7
  • 10
  • s/oldes/oldest/. Good answer, +1. – David Conrad May 12 '14 at 14:48
  • 23
    **Total nitpick warning:** "The question is not very well put."—I disagree, taking "well put" to mean "clearly suggesting which knowledge gap the querent wants filled". In essence the question is "optimizing for X, pick A or B, and why? What happens underneath?", which at least to me suggests very clearly what the knowledge gap is. The flaw in the question, which you rightly point to and address very well, is that it's built on a faulty mental model. – Jonas Kölker May 12 '14 at 15:47
  • For `a = b + c * d;`, `a = b + (c * d);` would be [harmless] redundant parentheses. If they help you make the code more readable, fine. `a = (b + c) * d;` would be non-redundant parentheses -- they actually change the resulting parse tree and give a different result. Perfectly legal to do (in fact, necessary), but they are not the same as the implied default grouping. – Phil Perry May 12 '14 at 16:54
  • 1
    @OrangeDog true, its a shame Ben's comment brought out a few people who like to claim VMs are faster than native. – gbjbaanb May 12 '14 at 21:51
  • @PhilPerry: In your example, the change in meaning was obvious because the operators differed, but if the code had been e.g. `a+(b+c)` versus `(a+b)+c` the meanings could differ if the variables were of type `int` or `float` (though not, interestingly enough, `unsigned int`). – supercat May 13 '14 at 01:11
  • 1
    @JonasKölker: My opening sentence actually refers to the question as formulated in the title: one cannot really answer a question about whether the compiler inserts or removes parentheses, since that is based on a misconception of how compilers operate. But I agree it is quite clear which knowledge gap needs to be addressed. – Marc van Leeuwen May 13 '14 at 12:32
  • @supercat, the original question was asking about _redundant_ parentheses and their negative effects. I also offered an example of non-redundant parentheses, where they change the parse and the results. You could also talk about `a+(b+c)` vs `(a+b)+c`, but that's a gray area because compilers (parsers) are free to parse it either way, even though the results may differ due to arithmetic overflow or underflow. – Phil Perry May 13 '14 at 13:21
  • @PhilPerry: I believe that if `a`, `b`, and `c` are volatile a compiler would be required to read them once each, in arbitrary order, but that even though the expression `a+b+c` could be evaluated by reading `c` before `a` and `b`, the result of that read must be added to the sum of `a` and `b`. In the absence of that rule, it would frequently be necessary to add many "redundant" parentheses, especially with floating-point math [there are many cases where (a+b)+c will be nowhere near a+(b+c)]. – supercat May 13 '14 at 13:27
  • @supercat, when you say _volatile_, can I assume that you're talking about variables whose values may change asynchronously due to external agents? The only requirement then, that I'm aware of (in general, not specifically C++), is that they must be reloaded as close to the point of use as possible, to ensure we're working with the latest values. You can't use an "old" value hanging around in a register, or a previously calculated "`a+b`" value. Other than that, how does volatility enter in to `(a+b)+c` _vs_ `a+(b+c)`? Is this specified in C++ (it's not in many other languages)? – Phil Perry May 13 '14 at 14:15
  • Hey @PhilPerry, could you take your discussion with supercat somewhere else? My answer clearly says changing the parse tree _can_ change the semantics, so I don't see what a discussion about under which circumstances it may nevertheless not change the result, or maybe to the contrary do change the result, has anything to do with my answer. – Marc van Leeuwen May 13 '14 at 14:21
46

The parentheses are there solely for your benefit - not the compilers. The compiler will create the correct machine code to represent your statement.

FYI, the compiler is clever enough to optimise it away entirely if it can. In your examples, this would get turned into int a = 6; at compile time.

gbjbaanb
  • 48,354
  • 6
  • 102
  • 172
  • So I should not restraint from using parenthesis wherever it helps reading while not having any real effect? – Serge May 12 '14 at 09:42
  • 9
    Absolutely - stick as many parentheses in as you like and let the compiler do the hard work of figuring out what you want :) – gbjbaanb May 12 '14 at 09:47
  • 23
    @Serge true programming is more about readability of your code than about performance. You will hate yourself next year when you need to debug a crash and you only have "optimized" code to go by. – ratchet freak May 12 '14 at 09:48
  • 1
    @ratchetfreak, you're right yet I also know how to comment my code. int a = 6; // = (1 + 2) + 3 – Serge May 12 '14 at 09:53
  • 20
    @Serge I know that won't hold up after a year of tweaks, in time comments and code will go out of sync and then you end up with `int a = 8;// = 2*3 + 5` – ratchet freak May 12 '14 at 12:23
  • 21
    or from www.thedailywtf.com: `int five = 7; //HR made us change this to six...` – Mooing Duck May 12 '14 at 16:29
  • Ah yes, the joys of dealing with Magic Numbers. – Phil Perry May 12 '14 at 16:46
  • 1
    Don't write `int a = 6; // = (1 + 2) + 3`. Write `int a = (1 + 2) + 3;` instead. – Marius Schulz May 13 '14 at 19:05
23

The answer to the question you actually asked is no, but the answer to the question you meant to ask is yes. Adding parentheses does not slow down the code.

You asked a question about optimisation, but parentheses have nothing to do with optimisation. The compiler applies a variety of optimisation techniques with the intention of improving either the size or speed of the generated code (sometimes both). For example, it might take the expression A^2 (A squared) and replace it by A x A (A multiplied by itself) if that is faster. The answer here is no, the compiler does nothing different in its optimisation phase depending on whether parentheses are present are not.

I think you meant to ask whether the compiler still generates the same code if you add unnecessary parentheses to an expression, in places that you think might improve readability. In other words, if you add parentheses is the compiler smart enough to take them out again rather than somehow generating poorer code. The answer is yes, always.

Let me say that carefully. If you add parentheses to an expression that are strictly unnecessary (have no effect whatever on the meaning or order of evaluation of an expression) the compiler will silently discard them and generate the same code.

However, there exist certain expressions where apparently unnecessary parentheses will actually change the order of evaluation of an expression and in that case the compiler will generate code to put into effect what you actually wrote, which might be different from what you intended. Here is an example. Don't do this!

short int a = 30001, b = 30002, c = 30003;
int d = -a + b + c;    // ok
int d = (-a + b) + c;  // ok, same code
int d = (-a + b + c);  // ok, same code
int d = ((((-a + b)) + c));  // ok, same code
int d = -a + (b + c);  // undefined behaviour, different code

So add parentheses if you want to, but make sure they really are unnecessary!

I never do. There is a risk of error for no real benefit.


Footnote: unsigned behaviour happens when a signed integer expression evaluates to a value that is outside the range that it can express, in this case -32767 to +32767. This is a complex topic, out of scope for this answer.

david.pfx
  • 8,105
  • 2
  • 21
  • 44
  • Undefined behavior in the last line is because a signed short only has 15 bits after the sign, so a max size of 32767, right? In that trivial example, the compiler should warn about overflow, right? +1 for a counter example, either way. If they were parameters to a function, you wouldn't get a warning. Also, if `a` can truly be unsigned, leading your calculation with `-a + b` could easily overflow as well, if `a` were negative and `b` positive. – Patrick M May 12 '14 at 13:53
  • @PatrickM: see edit. Undefined behaviour means the compiler can do what it likes, including issuing a warning, or not. Unsigned arithmetic does not produce UB, but is reduced modulo the next higher power of two. – david.pfx May 12 '14 at 14:15
  • The expression `(b+c)` in the last line will promote its arguments to `int`, so unless the compiler defines `int` to be 16 bits (either because it's ancient or targets a small microcontroller) the last line would be perfectly legitimate. – supercat May 13 '14 at 01:15
  • @supercat: I don't think so. The common type and type of the result should be short int. If that's not something that's ever been asked, perhaps you'd like to post a question? – david.pfx May 13 '14 at 03:36
  • @david.pfx: The rules of C arithmetic promotions are pretty clear: everything smaller than `int` gets promoted to `int` unless that type would be unable to represent all its values in which case it gets promoted to `unsigned int`. Compilers may omit the promotions *if all defined behaviors would be the same as if the promotions were included*. On a machine where 16-bit types behave as a wrapping abstract algebraic ring, (a+b)+c and a+(b+c) will be equivalent. If `int` were a 16-bit type that trapped on overflow, however, then there would be cases where one of the expressions... – supercat May 13 '14 at 03:43
  • ...would cause overflow and the other did not; the standard imposes no requirements on what must happen if `int` overflows. – supercat May 13 '14 at 03:43
  • @supercat: THis is C++, not C, and I don't see in the standard any language that says this. If you do, it would be better to ask/answer a question with the details. – david.pfx May 13 '14 at 04:17
  • @david.pfx: I was unaware of that as a point of divergence between C and C++, but you may be right. It would seem a little surprising, though, since early C++ compilers would have promoted in the cases where C does, and doing otherwise would seem a breaking change. – supercat May 13 '14 at 05:50
  • This seems to mention the promotion in C++: https://www.securecoding.cert.org/confluence/display/cplusplus/INT02-CPP.+Understand+integer+conversion+rules see ISO/IEC 14882:2003(E – BlueTrin May 14 '14 at 15:09
7

Brackets are only there for you to manipulate the order of operator precedence. Once compiled, brackets no longer exists because the run-time doesn't need them. The compilation process removes all of the brackets, spaces and other syntactic sugar that you and I need and changes all of the operators into something [far] simpler for the computer to execute.

So, where you and I might see ...

  • "int a = ((1 + 2) + 3);"

... a compiler might emit something more like this:

  • Char[1]::"a"
  • Int32::DeclareStackVariable()
  • Int32::0x00000001
  • Int32::0x00000002
  • Int32::Add()
  • Int32::0x00000003
  • Int32::Add()
  • Int32::AssignToVariable()
  • void::DiscardResult()

The program is executed by starting at the beginning and executing each instruction in turn.
Operator precedence is now "first-come, first served".
Everything is strongly typed, because the compiler worked all that out while it was tearing the original syntax apart.

OK, it's nothing like the stuff that you and I deal with, but then we're not running it!

Phill W.
  • 11,891
  • 4
  • 21
  • 36
  • 4
    There's not a single C++ compiler which will produce anything even remotely like this. Generally they produce actual CPU code, and assembly doesn't look this either. – MSalters May 12 '14 at 13:19
  • 3
    the intent here was to demonstrate the difference in structure between the code as written and the compiled output. even here most people would not be able to read actual machine code or assembly – DHall May 12 '14 at 14:04
  • @MSalters Nitpicking clang would emit 'something like that' if you treat LLVM ISA as 'something like that' (it's SSA not stack based). Given that it's possible to write JVM backend for LLVM and JVM ISA (AFAIK) is stack based clang->llvm->JVM would look very similar. – Maciej Piechotka May 12 '14 at 23:29
  • I don't think the LLVM ISA has the ability to define names stack variables using runtime string literals (just the first two instructions). This is seriously mixing up run-time and compile-time. The distinction matters because this question is exactly about that confusion. – MSalters May 13 '14 at 15:06
6

Depends if it is floating point or not:

  • In floating point arithmetic addition is not associative so the optimizer can't reorder the operations (unless you add the fastmath compiler switch).

  • In integer operations they can get reordered.

In your example both will run the exact same time because they will compile to the exact same code (addition is evaluated left to right).

however even java and C# will be able optimize it, they will just do it at runtime.

ratchet freak
  • 25,706
  • 2
  • 62
  • 97
  • +1 for bringing up that floating point operations aren't associative. – Doval May 12 '14 at 12:37
  • In the example of the question the parentheses do not alter the default (left) associativity, so this point is moot. – Marc van Leeuwen May 12 '14 at 13:37
  • 1
    About last sentence, I don't think so. In both java a and c# the compiler will produce optimized bytecode / IL. Runtime is not affected. – Stefano Altieri May 12 '14 at 14:16
  • IL doesn't work on expressions of this sort, the instructions take a certain number of values from a stack, and return a certain number of values (generally 0 or 1) to the stack. Talking of this sort of thing being optimised at runtime in C# is nonsense. – Jon Hanna May 14 '14 at 00:38
6

The typical C++ compiler translates to machine code, not C++ itself. It removes useless parens, yes, because by the time it's done, there are no parens at all. Machine code doesn't work that way.

The Spooniest
  • 2,160
  • 12
  • 9
5

Both code end up hard coded as 6 :

movl    $6, -4(%rbp)

Check for yourself here

MonoThreaded
  • 159
  • 3
1

No, but yes, but maybe, but maybe the other way, but no.

As people have already pointed out, (assuming a language where addition is left-associative, such as C, C++, C# or Java) the expression ((1 + 2) + 3) is exactly equivalent to 1 + 2 + 3. They're different ways of writing something in the source code, that would have zero effect on the resulting machine code or byte code.

Either way the result is going to be an instruction to e.g. add two registers and then add a third, or take two values from a stack, add it, push it back, then take it and another and add them, or add three registers in a single operation, or some other way to sum three numbers depending on what is most sensible at the next level (the machine code or byte code). In the case of byte code, that in turn will likely undergo a similar re-structuring in that e.g. the IL equivalent of this (which would be a series of loads to a stack, and popping pairs to add and then push back the result) would not result in a direct copy of that logic at the machine code level, but something more sensible for the machine in question.

But there is something more to your question.

In the case of any sane C, C++, Java, or C# compiler, I would expect the result of both of the statements you give to have the exact same results as:

int a = 6;

Why should the resultant code waste time doing math on literals? No changes to the state of the program will stop the result of 1 + 2 + 3 being 6, so that's what should go in the code being executed. Indeed, maybe not even that (depending on what you do with that 6, maybe we can throw the whole thing away; and even C# with it's philosophy of "don't optimise heavily, since the jitter will optimise this anyway" will either produce the equivalent of int a = 6 or just throw the whole thing away as unnecessary).

This though leads us to a possible extension of your question though. Consider the following:

int a = (b - 2) / 2;
/* or */
int a = (b / 2)--;

and

int c;
if(d < 100)
  c = 0;
else
  c = d * 31;
/* or */
int c = d < 100 ? 0 : d * 32 - d
/* or */
int c = d < 100 && d * 32 - d;
/* or */
int c = (d < 100) * (d * 32 - d);

(Note, this last two examples are not valid C#, while everything else here is, and they are valid in C, C++ and Java.)

Here again we've exactly equivalent code in terms of output. As they aren't constant expressions, they won't be calculated at compile time. It's possible that one form is faster than another. Which is faster? That would depend on the processor and perhaps on some rather arbitrary differences in state (particularly since if one is faster, it's not likely to be a lot faster).

And they aren't entirely unrelated to your question, as they are mostly about differences in the order in which something is conceptually done.

In each of them, there's a reason to suspect that one may be faster than the other. Single decrements may have a specialised instruction, so (b / 2)-- could indeed be faster than (b - 2) / 2. d * 32 could perhaps be produced faster by turning it into d << 5 so making d * 32 - d faster than d * 31. The differences between the last two are particularly interesting; one allows some processing to be skipped in some cases, but the other avoids the possibility of branch mis-prediction.

So, this leaves us with two questions: 1. Is one actually faster than the other? 2. Will a compiler convert the slower into the faster?

And the answer is 1. It depends. 2. Maybe.

Or to expand, it depends because it depends on the processor in question. Certainly there have existed processors where the naïve machine-code equivalent of one would be faster than the naïve machine-code equivalent of the other. Over the course of the history of electronic computing, there hasn't been one that was always the faster, either (the branch mis-prediction element in particular wasn't relevant to many when non-pipelined CPUs were more common).

And maybe, because there are a bunch of different optimisations that compilers (and jitters, and script-engines) will do, and while some may be mandated in certain cases, we'll generally be able to find some pieces of logically equivalent code that even the most naïve compiler has exactly the same results and some pieces of logically equivalent code where even the most sophisticated produces faster code for one than for the other (even if we have to write something totally pathological just to prove our point).

It might seems like a very tiny optimization concern,

No. Even with more complicated differences than those I give here, it seems like an absolutely minute concern that has nothing to do with optimisation. If anything, it's a matter of pessimisation since you suspect the harder to read ((1 + 2) + 3 could be slower than the easier to read 1 + 2 + 3.

but choosing C++ over C#/Java/... is all about optimizations (IMHO).

If that's really what choosing C++ over C# or Java was "all about" I'd say people should burn their copy of Stroustrup and ISO/IEC 14882 and free up the space of their C++ compiler to leave room for some more MP3s or something.

These languages have different advantages over each other.

One of them is that C++ is still generally faster and lighter on memory use. Yeah, there are examples where C# and/or Java are faster and/or have better application-lifetime use of memory, and these are becoming more common as the technologies involved improve, but we can still expect the average program written in C++ to be a smaller executable that does its job faster and using less memory than the equivalent in either of those two languages.

This isn't optimisation.

Optimisation is sometimes used to mean "making things go faster". It's understandable, because often when we really are talking about "optimisation" we are indeed talking about making things go faster, and so one has become a shorthand for the other and I'll admit I misuse the word that way myself.

The correct word for "making things go faster" is not optimisation. The correct word here is improvement. If you make a change to a program and the sole meaningful difference is that it is now faster, it isn't optimised in any way, it's just better.

Optimisation is when we make an improvement in regards to a particular aspect and/or particular case. Common examples are:

  1. It's now faster for one use case, but slower for another.
  2. It's now faster, but uses more memory.
  3. It's now lighter on memory, but slower.
  4. It's now faster, but harder to maintain.
  5. It's now easier to maintain, but slower.

Such cases would be justified if, e.g.:

  1. The faster use case is more common or more severely hampered to begin with.
  2. The program was unacceptably slow, and we've lots of RAM free.
  3. The program was grinding to a halt because it used so much RAM it spent more time swapping than executing its super-fast processing.
  4. The program was unacceptably slow, and the harder to understand code is well-documented and relatively stable.
  5. The program is still acceptably fast, and the more understandable code-base is cheaper to maintain and allows for other improvements to be more readily made.

But, such cases would also not be justified in other scenarios: The code hasn't been made better by an absolute infallible measure of quality, it's been made better in a particular regard that makes it more suitable for a particular use; optimised.

And choice of language does have an effect here, because speed, memory use, and readability can all be affected by it, but so can compatibility with other systems, availability of libraries, availability of runtimes, maturity of those runtimes on a given operating system (for my sins I somehow ended up having Linux and Android as my favourite OSs and C# as my favourite language, and while Mono is great, but I still come up against this one quite a bit).

Saying "choosing C++ over C#/Java/... is all about optimizations" only makes sense if you think C++ really sucks, because optimisation is about "better despite..." not "better". If you think C++ is better despite itself, then the last thing you need is to worry about such minute possible micro-opts. Indeed, you're probably better off abandoning it at all; happy hackers are a quality to optimise for too!

If however, you're inclined to say "I love C++, and one of the things I love about it is squeezing out extra cycles", then that's a different matter. It's still a case that micro-opts are only worth it if they can be a reflexive habit (that is, the way you tend to code naturally will be the faster more often than it is the slower). Otherwise they're not even premature optimisation, they're premature pessimisation that just make things worse.

Jon Hanna
  • 2,115
  • 12
  • 15
0

Parentheses are there to tell the compiler in which order expressions should be evaluated. Sometimes they are useless (except they improve or worsen readability), because they specify the order that would be used anyway. Sometimes they change the order. In

int a = 1 + 2 + 3;

practically every language in existence has a rule that the sum is evaluated by adding 1 + 2, then adding the result plus 3. If you wrote

int a = 1 + (2 + 3);

then the parenthesis would force a different order: First adding 2 + 3, then adding 1 plus the result. Your example of parentheses produces the same order that would have been produced anyway. Now in this example, the order of operations is slightly different, but the way that integer addition works, the outcome is the same. In

int a = 10 - (5 - 4);

the parentheses are critical; leaving them out would change the result from 9 to 1.

After the compiler has determined what operations are performed in which order, the parenthesis are completely forgotten. All that the compiler remembers at this point is which operations to perform in which order. So there is actually nothing that the compiler could optimise here, the parentheses are gone.

gnasher729
  • 42,090
  • 4
  • 59
  • 119
  • `practically every language in existence`; except APL: Try (here)[tryapl.org] entering `(1-2)+3` (2), `1-(2+3)` (-4) and `1-2+3` (also -4). – tomsmeding May 13 '14 at 14:12
0

I agree with much of what has been said, however…the over-arch here is that the parentheses are there to coerce order of operation…which the compiler is absolutely doing. Yes, it produces machine code…but, that is not the point and is not what is being asked.

The parentheses are indeed gone : as has been said, they are not part of machine code, which is numbers and not a thing else. Assembly code is not machine code, it is semi-human readable and contains the instructions by name -- not opcode. The machine runs what are called opcodes -- numeric representations of assembly language.

Languages like Java fall into an in-between area as they compile only partially on the machine that produces them. They are compiled to machine specific code on the machine that runs them, but that makes no difference to this question -- the parentheses are still gone after the first compile.

jinzai
  • 111
  • 1
  • 1
    I am not sure that this answers the question. The supporting paragraphs are more confusing than helpful. How is Java compiler relevant to the C++ compiler? – Adam Zuckerman May 12 '14 at 15:20
  • OP asked if the parentheses were gone…I said that they were and further explained that executable code is just numbers representing opcodes. Java was brought up in another answer. I think it answers the question just fine…but that is only my opinion. Thanks for replying. – jinzai May 12 '14 at 18:01
  • 3
    Parentheses don't force "order of operation". They change precedence. So, in `a = f() + (g() + h());`, the compiler is free to call `f`, `g`, and `h` in that order (or in any order it pleases). – Alok May 13 '14 at 06:27
  • I have some disagreement with that statement…you absolutely can coerce order of operation with parentheses. – jinzai Jun 06 '14 at 17:18
0

Compilers, regardless of the language, translate all infix math to postfix. In other words, when the compiler sees something like:

((a+b)+c)

it translates it into this:

 a b + c +

This is done because while infix notation is easier for people to read, the postfix notation is much closer to the actual steps the computer has to take to get the job done (and because there is already a well-developed algorithm for it.) By definition, postfix has eliminates all issues with order-of-operations or parentheses, which naturally makes things much easier when actually writing the machine code.

I recommend the wikipedia article on Reverse Polish Notation for more information on the subject.

  • 5
    That's an incorrect assumption about how compilers translate operations. You are for example assuming a stack machine here. What if you had a vector processor? what if you had a machine with a large amount of registers? – Ahmed Masud May 12 '14 at 15:46