6

Will the 2nd line of the following code

int bar;
int foo = bar * 3 * 5;

be optimized to

int bar;
int foo = bar * 15;

Or even more:

int foo = 3 * bar * 5;

can be optimized?

The purpose is actually to ask if I can just write

int foo = bar * 3 * 5;

instead of

int foo = bar * (3 * 5);

to save the parentheses. (and the relieve the need to manually manipulate those constant ordering => and in many case grouping constants with related variables are more meaningful rather than grouping constants for optimization)

Robin Hsu
  • 4,164
  • 3
  • 20
  • 37
  • Try it and see. Compilers are allowed to do whatever they like so long as the program's output is correct. – M.M Oct 02 '15 at 04:02
  • If you hover over the `*` in `3 * 5` you should see that it is already evaluated. This is using VS2015. – bku_drytt Oct 02 '15 at 04:02
  • Probably, yes it will do the optimization, but it depends on the compiler. – Erik Eidt Oct 02 '15 at 04:02
  • Why on earth would you even consider `int foo = bar * (3 * 5);` – M.M Oct 02 '15 at 04:02
  • For what it's worth gcc optimises `int foo = 3 * bar * 5;` as `bar * 15`. – user657267 Oct 02 '15 at 04:03
  • 3
    @user657267, if `gcc` optimises `int foo = 3 * bar * 5;` as `bar * 45`, it would be severely deficient :-) Moot now that you've edited the comment of course. – paxdiablo Oct 02 '15 at 04:05
  • 1
    @pedants sorry for the typo. – user657267 Oct 02 '15 at 04:07
  • 2
    @M.M: Maybe not explicitly, but if you're using `#define`s to name your constants instead of spewing magic numbers everywhere, you might see `int foo = bar * FROBNICATE_COUNT * WIDGET_HEADROOM;` (I'm having fun with my names) where the `#define` causes the preprocessor to generate `3` and `5` respectively. – ShadowRanger Oct 02 '15 at 04:09
  • 1
    With an uninitialized variable in the mix the optimizations don't matter. But presumably you meant to only indicate the variable's type. – Cheers and hth. - Alf Oct 02 '15 at 04:11
  • 1
    @ShadowRanger: *"but if you're using `#define`s to name your constants instead of spewing magic numbers everywhere*" and despite best practice being to avoid unnecessary use of preprocessor defines when `const` variables would do as well (and I know that's not everywhere, but encouraging macros indiscriminately is not good).... – Tony Delroy Oct 02 '15 at 05:05
  • 1
    @TonyD: Not really the point. `#define` or `static const`, optimizing compilers will usually substitute in literal values when practical, so the example of `bar * 3 * 5` is just reached during compilation instead of preprocessing. Personally, I don't particularly care much about simple `#define`s; true macros get dangerous, but getting religious about `#define`-ed constants is ignoring how people actually write code. – ShadowRanger Oct 02 '15 at 05:14
  • You can try with https://gcc.godbolt.org/ – curiousguy Oct 02 '15 at 05:30
  • @ShadowRanger: sure it's not the point, but you brought it up. I've no interest in your anecdotal coding experiences, so "agree to disagree" etc. etc.. – Tony Delroy Oct 02 '15 at 05:33
  • @TonyD: I brought it up because someone was asking why you'd have `3 * 5` written in code instead of just `15`. And the preprocessor case happens to literally produce that exact input to the compiler; `static const` is more complicated, so it was less useful for illustration. I actually agree with you, but nitpicking an example that is targeted to the question being addressed is irksome. – ShadowRanger Oct 02 '15 at 05:37
  • 1
    @ShadowRanger *"instead of spewing magic numbers everywhere"* went beyond simple illustration into claiming better practice - IMHO that makes the recommendation worth getting right. *"because someone was asking why you'd have 3 * 5"* - whom? FWIW, M.M. asked about parenthesising the `3 * 5`, which you didn't address in your macro-substitution example. – Tony Delroy Oct 02 '15 at 05:44
  • @M.M "Try it and see" is a terrible approach. It doesn't tell you anything about other compilers, other versions of the same compiler or even the same version applied to other programs. – martinkunev Feb 27 '23 at 15:47

3 Answers3

7

Almost all compilers will do it for integers, because even if a constant collapse might overflow in a different way, overflow may be ignored by the standard, so they can do what they like.

It often will not work for floating point values if it's adhering to strict floating point math; the order of evaluation with floating point math can affect the outcome, so strict compliance can't reorder floating point math.

5.1.2.3 Program execution

[#1] The semantic descriptions in this International Standard describe the behavior of an abstract machine in which issues of optimization are irrelevant.

[#3] In the abstract machine, all expressions are evaluated as specified by the semantics.

[#13] EXAMPLE 5 Rearrangement for floating-point expressions is often restricted because of limitations in precision as well as range. The implementation cannot generally apply the mathematical associative rules for addition or multiplication, nor the distributive rule, because of roundoff error, even in the absence of overflow and underflow. (Source)

It's not describing the use with constants precisely, but it's clearly noting that seemingly equivalent operations are not actually equivalent in the bizarro world that is floating point arithmetic (e.g. x / 5.0 cannot be translated to x * 0.2 with complete equivalence, x + x * y cannot be equivalently represented as x * (1.0 + y)).

Community
  • 1
  • 1
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • "strict compliance can't reorder floating point math", can you add a supporting quote from the standard, please. It's news to me. – Cheers and hth. - Alf Oct 02 '15 at 04:12
  • @Alf: Done. I can't find a specific citation for it in the standard for the case of constants, but there is [a summary here](https://gcc.gnu.org/ml/gcc/2004-03/msg01425.html) by one of the gcc devs about the state of various languages in 2004. – ShadowRanger Oct 02 '15 at 04:31
  • 2
    one example for floating-point case: [Why doesn't GCC optimize `a*a*a*a*a*a` to `(a*a*a)*(a*a*a)`?](http://stackoverflow.com/q/6430448/995714) – phuclv Oct 02 '15 at 05:49
  • Will compilers really do it for integers? I'm thinking of something like `1000000000 / 64 * 8` which doesn't overflow but would overflow if the multiplication is done first. I'm assuming the actual values are only known at runtime. – martinkunev Feb 27 '23 at 15:52
  • @martinkunev: Division (and subtraction) are not commutative operators; the question is asking about reordering commutative operators (e.g. `+`/`*`). And of course, if all the operands are runtime values, then the question's point about optimizing constants wouldn't apply either. – ShadowRanger Feb 27 '23 at 16:37
  • @ShadowRanger I was thinking in more general terms and less about the specific question about `+` and `*` and constants. Mathematically `1000000000 / 64 * 8 = 1000000000 * 8 / 64`. This is what I mean by reordering here and I suppose it can be useful for other optimizations (even if nothing reduces to a compile-time constant). – martinkunev Feb 27 '23 at 16:53
  • 1
    @martinkunev: Yes, but you asked "Will it really do this?" as if my post was claiming it would. It won't. There'd be no benefit to the reordering here (you'd still have to do the same number and type of operations), and (as you note) it would risk overflow by reordering. If there were a case where performance gains were possible, compilers still couldn't reorder non-commutative operations unless they can prove (e.g. because some of the inputs are expanded from smaller types, or the set of values they can take is somehow known) that the result won't overflow. – ShadowRanger Feb 27 '23 at 20:27
3

Here's an example of what an optimizer will do. Compiling this code with g++ 4.9.2 using -O2:

int calculate(int bar)     
{
    return bar*3*5;
}

is translated into this assembly code:

movl    %edi, %eax        # copy argument into eax
sall    $4, %eax          # shift eax left 4 bits
subl    %edi, %eax        # subtract original value from eax
ret                       # return (with eax as result)

Not only did it not do two multiplications, it didn't even do one. It converted the multipication by 15 into something equivalent to this:

int calculate(int bar)     
{
    return (bar<<4)-bar;
}
Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • That's `-O0` output. No self-respecting optimizing compiler would spill edi to the stack like that. Also, `-fomit-frame-pointer` has been the default for a while now. Use `echo 'int f(int n){return n*3*5;}' | gcc -xc - -O3 -S -o-`, or better, [put it on godbolt](https://goo.gl/55R2gB). The heart of the function compiles to the same 3 mov/shl/sub instructions from your listing, though. – Peter Cordes Oct 02 '15 at 08:57
  • @PeterCordes: Thanks, I had a mistake in my build. It's fixed now. – Vaughn Cato Oct 02 '15 at 13:36
2

A given implementation may or may not optimise any of those expressions. If you really want to know what it's doing for a given set of inputs, examine the generated assembler code.

But there's no guarantee you'll get the same optimisation from another compiler, the same compiler with different options or even the exact same compiler/options on Tuesday a week from now.

The general rule to follow is the "as if" rule, the compiler does things as if it was doing exactly what is specified in the standard. That doesn't mean it has to do it in any given way.

In other words, a compiler if free to do whatever it wants as long as it has the same effect as what the standard mandates.

The standard actually starts focusing on this aspect very early on, in the definitions section 3.4, where it defines behaviour as the "external appearance or action", and further examples pepper the document throughout.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953