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).

22
  • 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. May 12, 2014 at 10:29
  • 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, 2014 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, 2014 at 11:53
  • 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. May 12, 2014 at 13:10
  • 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). May 12, 2014 at 20:55

11 Answers 11

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).

10
  • s/oldes/oldest/. Good answer, +1. May 12, 2014 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. May 12, 2014 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, 2014 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, 2014 at 21:51
  • 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. May 13, 2014 at 12:32
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.

8
  • 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, 2014 at 9: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. May 12, 2014 at 9:48
  • 1
    @ratchetfreak, you're right yet I also know how to comment my code. int a = 6; // = (1 + 2) + 3
    – Serge
    May 12, 2014 at 9: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 May 12, 2014 at 12:23
  • 21
    or from www.thedailywtf.com: int five = 7; //HR made us change this to six... May 12, 2014 at 16:29
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.

9
  • 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, 2014 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, 2014 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, 2014 at 1: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, 2014 at 3: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, 2014 at 3:43
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!

4
  • 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, 2014 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, 2014 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. May 12, 2014 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, 2014 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.

4
  • +1 for bringing up that floating point operations aren't associative.
    – Doval
    May 12, 2014 at 12:37
  • In the example of the question the parentheses do not alter the default (left) associativity, so this point is moot. May 12, 2014 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. May 12, 2014 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, 2014 at 0: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.

5

Both code end up hard coded as 6 :

movl    $6, -4(%rbp)

Check for yourself here

2
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.

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.

1
  • 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, 2014 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.

4
  • 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? May 12, 2014 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, 2014 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, 2014 at 6:27
  • I have some disagreement with that statement…you absolutely can coerce order of operation with parentheses.
    – jinzai
    Jun 6, 2014 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.

1
  • 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? May 12, 2014 at 15:46

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