7

The code I have looks like this (all uses of done shown):

bool done = false;
for(int i = 0; i < big; i++)
{
  ...
  for(int j = 0; j < wow; j++)
  {
    ...
    if(foo(i,j))
    {
       done = true;
       break;
    }
    ...
  }
  if(done) break;
  ...
}

will any compilers convert it to this:

for(int i = 0; i < big; i++)
{
  ...
  for(int j = 0; j < wow; j++)
  {
    ...
    if(foo(i,j))
      goto __done; // same as a labeled break if we had it
    ...
  }
  ...
}
__done:;

Note: While I'm mostly interested in if the if(done)break; gets bypassed and removed as dead code, I'm also interested in if it and done gets removed altogether.

BCS
  • 75,627
  • 68
  • 187
  • 294
  • 3
    By the way, you shouldn't define any symbols that start with two underscores like that; such symbols are reserved. – Mike Seymour Jun 16 '10 at 02:04
  • 8
    The symbol would be the result of an optimization pass, e.i. generated by the compiler. I used that name *because* it indicates a reserved/internal name. – BCS Jun 16 '10 at 04:47
  • And the question: why isn't this tucked away in a function ? You could use a `return` then ;) – Matthieu M. Jun 16 '10 at 07:00
  • The labelled break is known as the GOTO statement in C/C++, and is suitable for your situation, unless you want to break your code into separate functions. – fabspro May 18 '12 at 16:00
  • @fabspro: for the sake of the question, assume the use of `goto` is forbidden (by some PHB, for example). Same goes for putting the loops in another function. – BCS May 18 '12 at 16:42
  • @BCS Ahh yes, the bane of the programmer's life. This question has been interesting to read – fabspro May 19 '12 at 17:20

4 Answers4

14

Obviously this depends on the compiler. The best thing to do when you're unsure is to view the compiler's assembly output (all popular compilers have a switch for this). Even if you aren't familiar with assembly, you can at least compare the debug version with the optimized version.

That being said, this is one of the few situations where goto is NOT a bad idea. Feel free to use it to break out of inner loops.

Edit

Just tried the following in VS2010 and it does indeed optimize the outer conditional:

bool done = false;
for(int i = 0; i < 10; i++)
{
    for(int j = 0; j < 10; j++)
    {
        if(i == 7 && j == 3)
        {
            done = true;
            break;
        }
    }
    if(done) break;
}
return 0;
Cogwheel
  • 22,781
  • 4
  • 49
  • 67
  • 16
    +1 for pragmatism. Far too many people have lost sight of the fact that gotos, breaks, multiple return points from functions, and other such things, are bad _only when they make the code hard to understand._ Judicious use of such things is fine. – paxdiablo Jun 16 '10 at 01:32
  • 1
    Bolded to make sure no one misses it :) – Cogwheel Jun 16 '10 at 01:37
  • Yah, a goto is legit there. OTOH a labeled break is even better and if it avoids me having to defend it in a code review, I'll live with the compiler doing some magic for me. – BCS Jun 16 '10 at 04:49
  • I think that most compilers can optimize it quite easily since the code ends to resemble: `set var, true; goto innerloopend (*); ... innerloopend: cmp var, true; beq outerloopend` and "tracking" var it is clear that in "goto innerloopend `(*)` will result always in a goto outerloopend" since `cmp var, true` will be always true when reached from `(*)`. Easy optimization for most compilers – ShinTakezou Jun 16 '10 at 07:20
  • well, i think its not that "easy" for the compiler to find this (can you please tell me how) but its good that even msvc++ finds it (even if it can't optimize that much in general). – Quonux Jul 14 '10 at 21:51
7

GNU compiler does just that, starting with optimization level -O1 (I am using gcc 4.5.1 on x86_64)

call    _Z3fooii  // al = foo(i,j)
testb   %al, %al
jne .L14
...

where .L14 is the label placed exactly where you placed __done:

A better question might be: which modern compiler does not perform this optimization?

Cubbi
  • 46,567
  • 13
  • 103
  • 169
4

I'm not trying to be snarky, but...does it matter? In general, I think it's best to let compilers to their job, and that job is to produce the "best" (note that "best" may vary depending on your needs) compiled code given your source code. Any performance considerations in your code should be identified with a profiler and good working knowledge of algorithmic complexity.

If you're just curious, then disregard this comment. But if your intention is to somehow optimize your code, then I think there are much better avenues.

kidjan
  • 1,471
  • 14
  • 16
  • 1
    A lot of compilers don't do that job very well -- at least, not as well as we assume they do. – Crashworks Jun 16 '10 at 02:23
  • @Crashworks, the problem with micro-optimizing to a particular compiler is just that, it's compiler-specific. Your code may regress significantly ob subsequent versions of said compiler if you actively prevent it from doing some new optimizations. It's better to optimize where you know compiler can't optimize due to language constraints (excessive object copying, aliasing, etc). On that tangent, it's hard to know which optimizations aren't done because the compiler is lacking, and which, because the language spec does not allow that. – Alex B Jun 16 '10 at 02:33
  • 2
    I somewhat agree, Crash, but I still think what's fundamentally important is understanding algorithmic complexity. The best compiler in the world can't fix some shoddy linear search, or poor string concatenation code. I find it more productive to think of compilers as a black box because it forces us to take accountability for things we have the most control over: our own code. – kidjan Jun 16 '10 at 02:33
  • 1
    @Crashworks, PS: http://www.linux-kongress.org/2009/slides/compiler_survey_felix_von_leitner.pdf – Alex B Jun 16 '10 at 02:34
  • 1
    Algorithms and memory hierarchy are definitely paramount! It's just worth remembering that compilers aren't perfect, and sometimes the profiler will turn up things that you really can improve on. – Crashworks Jun 16 '10 at 03:45
  • In this case the optimization is so trivial (and arguably easier to understand) that the question is it worth having to defend it re knee jerk reactions? – BCS Jun 16 '10 at 04:54
1

I've tried GCC 4.2.1 with the following:

// Prevent optimizing out calls to foo and loop unrolling:
extern int big, wow;
bool foo(int,int);

void
bar()
{
    int done = false;
    for(int i = 0; i < big; i++)
    {
        for(int j = 0; j < wow; j++)
        {
            if(foo(i,j))
            {
                done = true;
                break;
            }
        }
        if(done)
            break;
    }
}

...and it falls through straight to postamble with -O3:

  33:   e8 fc ff ff ff          call   34 <bar()+0x34> ; call to foo*
  38:   84 c0                   test   %al,%al
  3a:   74 e5                   je     21 <bar()+0x21> ; next loop iteration
  3c:   83 c4 10                add    $0x10,%esp
  3f:   5b                      pop    %ebx
  40:   5e                      pop    %esi
  41:   5d                      pop    %ebp
  42:   c3                      ret

*** This is from an unlinked object file, call 34 is actually call to foo.

Alex B
  • 82,554
  • 44
  • 203
  • 280
  • 1
    so `3a` branches directly to the ultimate end at `42`? – BCS Jun 16 '10 at 04:58
  • @BCS, 0x3a branches to the beginning of the loop if AL is zero (foo returned false), else it just continues to postamble (pops saved registers, restores previous stack frame) 3c, 3f, .. until ret – Alex B Jun 16 '10 at 05:11