开发者

C/C++: is GOTO faster than WHILE and FOR?

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");
    }
    


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


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.


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.


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.


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.


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.


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.


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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜