19

I know, that everybody hates GOTO and nobody recommends it. But that's not the point. I just want to know, which code is the fastest:

  1. the goto loop

    int i=3;
    loop:
    printf("something");
    if(--i) goto loop;
    
  2. the while loop

    int i=3;
    while(i--) {
        printf("something");
    }
    
  3. the for loop

    for(int i=3; i; i--) {
        printf("something");
    }
    
tshepang
  • 12,111
  • 21
  • 91
  • 136
Artur Iwan
  • 1,151
  • 2
  • 10
  • 17
  • 8
    You wrote all the code already ... why not just profile it? – Brian Roach Mar 20 '11 at 05:24
  • @BrianRoach: What do you mean by _profiling it_? Yes, I can run this loop 100000000 times and see, which of them would be the fastest.... but I would like to know the _universal_ answer, not only for my compiler & my processor. – Artur Iwan Mar 20 '11 at 05:28
  • @Mahesh: How do you mean it? A loop, that doesn't enter a loop? Is it connected with some cool ASM instruction? – Artur Iwan Mar 20 '11 at 05:30
  • Measure the amount of time it takes. Have your loops execute N times (Say, 1000), and measure how long it takes each to complete. – Brian Roach Mar 20 '11 at 05:30
  • @Mahesh: Look again. There is a condition, `i` only. It's the same as `i!=0`, but a little faster. – Artur Iwan Mar 20 '11 at 05:34
  • @BrianRoach: There would be only 1-2 assembly instruction difference per loop... the test won't be optimal, because there are some other processes ran simultaneously.... The only possibility would be to disable parallel processes during the test, by disabling the processor interruptions.. So far, it's too difficult for me :) – Artur Iwan Mar 20 '11 at 05:37
  • @Mahesh: `i` is supposed to start at 3, not 0! @Arturr: the condition `i` and `i!=0` is exactly the same to the compiler -- neither is faster. – Gabe Mar 20 '11 at 05:39
  • 2
    @Arturr There isn't a universal answer. Since the loops are functionally identical, your question is precisely about what the compiler will do with them. As Gabe says below, you'd expect a reasonable compiler to give you identical assembly code for all three loops. It's not required to do so, though, and you could write a compiler that made one of the loops much slower for no good reason. – David German Mar 20 '11 at 05:40
  • Why not look at the assmebler and see which is more efficient? – johnsyweb Mar 20 '11 at 05:41
  • 2
    In all likelihood these will all compile to the same assembly, performing an increment `cmp` and `jmp`. Or (with the given examples) at any level past `-O0` it will end up in just calling printf() directly 3 times. – John Ledbetter Mar 20 '11 at 05:41
  • 2
    I was tempted to -1 for the severely misguided question, but that isn't your fault. But now with your attitude, -1. *It doesn't matter*. Pick the correct one, let the compiler do it's job, finish the day. If it's a problem *as reported by your profiler*, look at the assembly and see if you can fix it, don't try to make ridiculous sweeping generalizations. Lastly, `goto` isn't bad "because of some smart guy, that said that", he said that *because it's bad*. Your flippancy towards the advice against it is unwarranted; there are better ways to express your intends than with a `goto`, period. – GManNickG Mar 20 '11 at 05:54
  • 3
    Asking such a question with a `printf` inside the loop is ridiculous. Your printf will take orders of magnitudes more time than your loop control. You'd really have to have some very optimized code inside the loop (no cache misses and stuff like that) before the handling of the control itself could becomes relevant. – Jens Gustedt Mar 20 '11 at 07:54
  • 2
    Also a side remark on "*only because of some smart guy, that said that*". That smart guy was one of the founders of CS and he became famous *because* he dared to say that in an environment where all was FORTRAN with computed `goto`s. Your statement is as smart as saying `E = m c^2` is famous because Einstein said it. It is the other way round. – Jens Gustedt Mar 20 '11 at 13:21
  • @Jens: I like your comment about `printf`. I don't like your comment about `goto` :-) Dijkstra never said what was attributed to him - his editor just made up that title. I believe Dijkstra wanted people to use their heads, but that's too subtle for editors. – Mike Dunlavey Mar 20 '11 at 17:08
  • ++ Not because it's a very good question, but as a welcome to StackOverflow. My first question wasn't that great either. There are lots of smart people around the world helping each other, and it's great that you'd like to join them. – Mike Dunlavey Mar 20 '11 at 17:19
  • @Mike, I didn't know that about the title of the "gotos are harmful" paper. I just didn't like the attitude of the OP in blaming the messenger. In any case Dijkstra made an important point with the contents of it, that one shouldn't mess around with control flow in unpredictable ways. And exactly as you say, he wanted people to think and to listen to what he had to say. – Jens Gustedt Mar 20 '11 at 18:21
  • @Jens: I must confess, I'm nearly a one-issue poster on SO, to the effect of "profilers considered harmful", except that a very few of them are not so bad, and especially I would like to see people engage their brains on the subject, and not just accept a headline as their religion, or at least not one I would give them. – Mike Dunlavey Mar 20 '11 at 18:40
  • @Mike: Just because someone disagrees with you doesn't mean we've taken a headline as religion, that's asinine. Sometimes headlines are *right*. – GManNickG Mar 21 '11 at 03:04

10 Answers10

21

Generally speaking, for and while loops get compiled to the same thing as goto, so it usually won't make a difference. If you have your doubts, you can feel free to try all three and see which takes longer. Odds are you'll be unable to measure a difference, even if you loop a billion times.

If you look at this answer, you'll see that the compiler can generate exactly the same code for for, while, and goto (only in this case there was no condition).

Community
  • 1
  • 1
Gabe
  • 84,912
  • 12
  • 139
  • 238
  • The 'no condition' in the referenced answer may be critical to the assertion in some cases, since otherwise the `goto` version may be translated to a conditional test followed by an unconditional branch rather than a direct conditional branch. – Clifford Mar 20 '11 at 11:21
8

Write short programs, then do this:

gcc -S -O2 p1.c 
gcc -S -O2 p2.c 
gcc -S -O2 p3.c 

Analyze the output and see if there's any difference. Be sure to introduce some level of unpredictability such that the compiler doesn't optimize the program away to nothing.

Compilers do a great job of optimizing these trivial concerns. I'd suggest not to worry about it, and instead focus on what makes you more productive as a programmer.

Speed and efficiency is a great thing to worry about it, but 99% of the time that involves using proper data structures and algorithms... not worrying about whether a for is faster than a while or a goto, etc.

Matthew
  • 47,584
  • 11
  • 86
  • 98
  • `-O3` would give you full optimization – Andrew May 27 '21 at 06:09
  • For those who try this, see my answer [here](https://stackoverflow.com/a/67717002/1599699) for more information on how to add comments for both g++ and clang++ so that you know which part of the assembly to look at... – Andrew May 27 '21 at 06:51
  • @Andrew that and `-flto` – user426 Sep 13 '21 at 09:37
7

The only time I've seen the argument made for goto was in one of W. Richard Stevens' articles or books. His point was that in a very time-critical section of code (I believe his example was the network stack), having nested if/else blocks with related error-handling code could be redone using goto in a way that made a valuable difference.

Personally, I'm not good enough a programmer to argue with Stevens' work, so I won't try. goto can be useful for performance-related issues, but the limits of when that is so are fairly strict.

Harper Shelby
  • 16,475
  • 2
  • 44
  • 51
  • 1
    OP was asking about goto compared to looping constructs, not if constructs. – Gabe Mar 20 '11 at 05:35
  • Yeah...and your answer was spot on. I dug up (granted, from memory) the only *related* discussion of goto vs. other constructs I have ever seen. My last sentence is the key to the 'answer' as such. I don't expect it to be the accepted answer, but I believe it is relevant to the discussion in general. – Harper Shelby Mar 20 '11 at 05:37
  • That, is a very good practice when you want to optimize some code and I use it either in some extreme cases, when speed is more important. But anyway, I would vote up for your unswer if I only could. – Artur Iwan Mar 20 '11 at 05:40
3

It is probably both compiler, optimiser and architecture specific.

For example the code if(--i) goto loop; is a conditional test followed by an unconditional branch. A compiler might simply generate corresponding code or it might be smart enough (though a compiler that did not have at least that much smarts may not be worth much), to generate a single conditional branch instruction. while(i--) on the other hand is already a conditional branch at the source level, so translation to a conditional branch at the machine level may be more likley regardless of the sophistication of the compiler implementation or optimiser.

In the end, the difference is likley to be minute and only relevant if a great many iterations are required, and the way you should answer this question is to build the code for the specific target and compiler (and compiler settings) of interest, and either inspect the resultant machine level code or directly measure execution time.

In your examples the printf() in the loop will dominate any timing in any case; something simpler in the loop would make observations of the differences easier. I would suggest an empty loop, and then declaring i volatile to prevent the loop being optimised to nothing.

Clifford
  • 88,407
  • 13
  • 85
  • 165
2

As long as you're generating the same flow of control as a normal loop, pretty nearly any decent compiler can and will produce the same code whether you use for, while, etc. for it.

You can gain something from using goto, but usually only if you're generating a flow of control that a normal loop simply can't (at least cleanly). A typical example is jumping into the middle of a loop to get a loop and a half construct, which most languages' normal loop statements (including C's) don't provide cleanly.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
2

There is should not be any significant difference between all the loops and the goto. Except the idea, that compiler more probably will not try to optimize the GOTO-things at all.

And there is not a lot of sense trying to optimize compiler-generated stuff in loops. It's more sense to optimize the code inside the loop, or reduce the number of iterations or so on.

voidlizard
  • 805
  • 5
  • 10
1

I think there will be some code after compiler under nornal condition.

In fact I think goto is very convenient sometimes, although it is hard to read.

Alan Tam
  • 63
  • 1
  • 7
1

On Linux, I compiled the code below into assembly using both g++ and clang++. For more information on how I did that, see here. (Short version: g++ -S -O3 filename.cpp clang++ -S -O3 filename.cpp, and some assembly comments you'll see below to help me out.)

Conclusion/TL;DR at the bottom.


First, I compared label: and goto vs. do {} while. You can't compare a for () {} loop with this (in good faith), because a for loop always evaluates the condition first. This time around, the condition is evaluated only after the loop code has been executed once.

#include <iostream>

void testGoto()
{
  __asm("//startTest");
  int i = 0;
  loop:
  std::cout << i;
  ++i;
  if (i < 100)
  {
    goto loop;
  }
  __asm("//endTest");
}
#include <iostream>

void testDoWhile()
{
  __asm("//startTest");
  int i = 0;
  do
  {
    std::cout << i;
    ++i;
  }
  while (i < 100);
  __asm("//endTest");
}

In both cases, the assembly is the exact same regardless of goto or do {} while, per compiler:

g++:

    xorl    %ebx, %ebx
    leaq    _ZSt4cout(%rip), %rbp
    .p2align 4,,10
    .p2align 3
.L2:
    movl    %ebx, %esi
    movq    %rbp, %rdi
    addl    $1, %ebx
    call    _ZNSolsEi@PLT
    cmpl    $100, %ebx
    jne .L2

clang++:

    xorl    %ebx, %ebx
    .p2align    4, 0x90
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
    movl    $_ZSt4cout, %edi
    movl    %ebx, %esi
    callq   _ZNSolsEi
    addl    $1, %ebx
    cmpl    $100, %ebx
    jne .LBB0_1
# %bb.2:

Then I compared label: and goto vs. while {} vs. for () {}. This time around, the condition is evaluated before the loop code has been executed even once.

For goto, I had to invert the condition, at least for the first time. I saw two ways of implementing it, so I tried both ways.

#include <iostream>

void testGoto1()
{
  __asm("//startTest");
  int i = 0;
  loop:
  if (i >= 100)
  {
    goto exitLoop;
  }
  std::cout << i;
  ++i;
  goto loop;
  exitLoop:
  __asm("//endTest");
}
#include <iostream>

void testGoto2()
{
  __asm("//startTest");
  int i = 0;
  if (i >= 100)
  {
    goto exitLoop;
  }
  loop:
  std::cout << i;
  ++i;
  if (i < 100)
  {
    goto loop;
  }
  exitLoop:
  __asm("//endTest");
}
#include <iostream>

void testWhile()
{
  __asm("//startTest");
  int i = 0;
  while (i < 100)
  {
    std::cout << i;
    ++i;
  }
  __asm("//endTest");
}
#include <iostream>

void testFor()
{
  __asm("//startTest");
  for (int i = 0; i < 100; ++i)
  {
    std::cout << i;
  }
  __asm("//endTest");
}

As above, in all four cases, the assembly is the exact same regardless of goto 1 or 2, while {}, or for () {}, per compiler, with just 1 tiny exception for g++ that may be meaningless:

g++:

    xorl    %ebx, %ebx
    leaq    _ZSt4cout(%rip), %rbp
    .p2align 4,,10
    .p2align 3
.L2:
    movl    %ebx, %esi
    movq    %rbp, %rdi
    addl    $1, %ebx
    call    _ZNSolsEi@PLT
    cmpl    $100, %ebx
    jne .L2

Exception for g++: at the end of the goto2 assembly, the assembly added:

.L3:
    endbr64

(I presume this extra label was optimized out of the goto 1's assembly.) I would assume that this is completely insignificant though.

clang++:

    xorl    %ebx, %ebx
    .p2align    4, 0x90
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
    movl    $_ZSt4cout, %edi
    movl    %ebx, %esi
    callq   _ZNSolsEi
    addl    $1, %ebx
    cmpl    $100, %ebx
    jne .LBB0_1
# %bb.2:

In conclusion/TL;DR: No, there does not appear to be any difference whatsoever between any of the possible equivalent arrangements of label: and goto, do {} while, while {}, and for () {}, at least on Linux using g++ 9.3.0 and clang++ 10.0.0.

Note that I did not test break and continue here; however, given that the assembly code generated for each of the 4 in any scenario was the same, I can only presume that they would be the exact same for break and continue, especially since the assembly is using labels and jumps for every scenario.

To ensure correct results, I was very meticulous in my process and also used Visual Studio Code's compare files feature.

Andrew
  • 5,839
  • 1
  • 51
  • 72
0

There are several niche verticals where goto is still commonly used as a standard practice, by some very very smart folks and there is no bias against goto in those settings. I used to work at a simulations focused company where all local fortran code had tons of gotos, the team was super smart, and the software worked near flawlessly.

So, we can leave the merit of goto aside, and if the question merely is to compare the loops, then we do so by profiling and/or comparing the assembly code. That said however, the question includes statements like printf etc. You can't really have a discussion about loop control logic optimization when doing that. Also, as others have pointed out, the given loops will all generate VERY similar machine codes.

All conditional branches are considered "taken" (true) in pipelined processor architectures anyway until decode phase, in addition to small loops being usually expanded to be loopless. So, in line with Harper's point above, it is unrealistic for goto to have any advantage whatsoever in simple loop control (just as for or while don't have an advantage over each other). GOTO makes sense usually in multiple nested loops or multiple nested ifs, when adding the additional condition checked by goto into EACH of the nested loops or nested ifs is suboptimal.

When optimizing a search kind of operation in a simple loop, using a sentinal is sometimes more effective than anything else. Essentially, by adding a dummy value at the end of the array, you can avoid checking for two conditions (end of array and value found) to be just one condition (value found), and that saves on cmp operations internally. I am unaware if compilers automatically do that or not.

Josh
  • 189
  • 12
0

goto Loop:

start_Chicken:
{
    ++x;
    if (x >= loops)
        goto end_Chicken;
}
goto start_Chicken;

end_Chicken:
x = 0;

for Loop:

for (int i = 0; i < loops; i++)
{

}

while Loop:

while (z <= loops)
{

    ++z;
}
z = 0;

Image from results

While loop in any situation with more mixed tests had as minimal but still better results.

Olli V
  • 19
  • 1
  • You gotta be careful doing "parallel" profiling in this manner; sometimes the order of one before the other changes the results... – Andrew May 27 '21 at 06:08