C/C++:GOTO 比 WHILE 和 FOR 更快吗?

发布于 2024-10-25 05:04:05 字数 374 浏览 7 评论 0原文

我知道,每个人都讨厌 GOTO,也没有人推荐它。但这不是重点。我只是想知道哪个代码最快:

  1. goto 循环

    int i=3;
    环形:
    printf("某事");
    if(--i) 转到循环;
    
  2. while 循环

    int i=3;
    而(我--){
        printf("某事");
    }
    
  3. for 循环

    for(int i=3; i; i--) {
        printf("某事");
    }
    

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

大姐,你呐 2024-11-01 05:04:07

我认为正常情况下编译后会有一些代码。

事实上我觉得 goto 有时很方便,虽然它很难读。

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.

不必在意 2024-11-01 05:04:07

在一些利基垂直领域,goto 仍然被一些非常非常聪明的人普遍用作标准实践,并且在这些设置中对 goto 没有偏见。我曾经在一家专注于模拟的公司工作,那里所有的本地 Fortran 代码都有大量的 goto,团队非常聪明,而且软件运行得几乎完美。

因此,我们可以把 goto 的优点放在一边,如果问题只是比较循环,那么我们可以通过分析和/或比较汇编代码来实现。不过,这就是说,问题包括像 printf 等语句。这样做时,您无法真正讨论循环控制逻辑优化。此外,正如其他人指出的那样,给定的循环都将生成非常相似的机器代码。

无论如何,在解码阶段之前,所有条件分支在流水线处理器架构中都被视为“已采用”(真),此外,小循环通常扩展为无循环。因此,根据 Harper 的上述观点,goto 在简单循环控制中具有任何优势是不现实的(就像 for 或 while 彼此没有优势一样)。当将 goto 检查的附加条件添加到每个嵌套循环或嵌套 if 中时,GOTO 通常在多个嵌套循环或多个嵌套 if 中有意义。

当在简单循环中优化搜索类型的操作时,使用标记有时比其他任何方法都更有效。本质上,通过在数组末尾添加一个虚拟值,您可以避免将两个条件(数组末尾和找到的值)检查为只是一个条件(找到的值),从而节省了内部 cmp 操作。我不知道编译器是否自动这样做。

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.

月隐月明月朦胧 2024-11-01 05:04:07

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;

来自结果的图像

在任何具有更多混合的情况下循环测试的结果虽然最少,但仍然更好。

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.

兮子 2024-11-01 05:04:06

一般来说,forwhile 循环被编译为与 goto 相同的东西,所以通常不会有什么区别。如果您有疑问,可以随意尝试这三种方法,看看哪个需要更长的时间。即使循环十亿次,您也很可能无法测量差异。

如果您查看 这个答案,您将看到编译器可以为 forwhilegoto 生成完全相同的代码code> (仅在这种情况下没有条件)。

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

‘画卷フ 2024-11-01 05:04:06

编写简短的程序,然后执行以下操作:

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

分析输出并查看是否有任何差异。请务必引入一定程度的不可预测性,以便编译器不会将程序优化得一无是处。

编译器在优化这些琐碎的问题方面做得很好。我建议不要担心它,而是专注于让你作为程序员更有效率的事情。

速度和效率是一件值得担心的大事,但 99% 的时间都涉及使用正确的数据结构和算法……不用担心 for 是否比 while 更快goto 等。

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.

说谎友 2024-11-01 05:04:06

我唯一一次看到关于 goto 的论证是在 W. Richard Stevens 的一篇文章或书中。他的观点是,在代码的非常时间关键部分(我相信他的例子是网络堆栈),可以使用 goto 重做具有相关错误处理代码的嵌套 if/else 块。方式带来了宝贵的改变。

就我个人而言,我作为一名程序员还不够好,无法与史蒂文斯的工作争论,所以我不会尝试。 goto 对于与性能相关的问题可能很有用,但是何时使用的限制相当严格。

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.

叹倦 2024-11-01 05:04:06

它可能是编译器、优化器和架构特定的。

例如,代码 if(--i) goto loop; 是一个条件测试,后跟一个无条件分支。编译器可能只是生成相应的代码,或者它可能足够智能(尽管至少不具备那么多智能的编译器可能没有多大价值),以生成单个条件分支指令。另一方面, while(i--) 已经是源代码级别的条件分支,因此无论编译器实现的复杂程度如何,都可能更倾向于翻译为机器级别的条件分支。优化器。

最后,差异可能很小,并且仅在需要大量迭代时才相关,并且您应该回答这个问题的方式是为感兴趣的特定目标和编译器(以及编译器设置)构建代码,并且检查生成的机器级代码或直接测量执行时间。

在您的示例中,循环中的 printf() 在任何情况下都将主导任何时间;循环中更简单的东西将使观察差异变得更容易。我建议使用一个空循环,然后声明 i volatile 以防止循环被优化为空。

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.

怀里藏娇 2024-11-01 05:04:06

只要您生成与普通循环相同的控制流,几乎任何像样的编译器都可以生成相同的代码,无论您使用 forwhile、等等。

您可以通过使用 goto 获得一些好处,但通常只有当您生成普通循环根本无法(至少干净地)无法生成的控制流时。一个典型的例子是跳到循环的中间以获得一个半循环构造,大多数语言的普通循环语句(包括 C 语言)都不能清楚地提供这种结构。

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.

醉酒的小男人 2024-11-01 05:04:06

所有循环和 goto 之间不应该有任何显着差异。除了这个想法之外,编译器很可能根本不会尝试优化 GOTO 事物。

尝试在循环中优化编译器生成的内容并没有多大意义。优化循环内部的代码,或者减少迭代次数等等更有意义。

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.

清引 2024-11-01 05:04:06

在 Linux 上,我使用 g++ 和 clang++ 将下面的代码编译成程序集。有关我如何做到这一点的更多信息,请参阅此处。 (简短版本:g++ -S -O3 filename.cpp clang++ -S -O3 filename.cpp,以及您将在下面看到的一些汇编注释来帮助我。)

结论/TL;DR 在底部。


首先,我比较了 label:gotodo {} while。您无法将 for () {} 循环与此进行比较(真诚地),因为 for 循环始终首先评估条件。这一次,只有在循环代码执行一次后才会评估条件。

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

在这两种情况下,无论 goto 还是 do {} while,每个编译器的程序集都是完全相同的:

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:

然后我比较了 label: 和 gotowhile {}for () {} 对比。这一次,在循环代码执行一次之前就评估了条件。

对于goto,我必须反转条件,至少是第一次。我看到了两种实现它的方法,所以我尝试了两种方法。

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

如上所述,在所有四种情况下,无论 goto 1 或 2、while {}for () {} ,程序集都是完全相同的code>,每个编译器,只有 1 个微小的 g++ 异常,可能毫无意义:

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++: 在 goto2 程序集的末尾,程序集添加了:(

.L3:
    endbr64

我认为这个额外的标签是从 中优化出来的goto 1 的程序集。)但我认为这完全无关紧要。

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:

结论/TL;DR:不,label:的任何可能的等效排列之间似乎没有任何区别>gotodo {} whilewhile {}for () {},至少在使用 g++ 的 Linux 上9.3.0 和 clang++ 10.0.0。

请注意,我没有在这里测试 breakcontinue ;但是,鉴于在任何情况下为 4 个中的每一个生成的汇编代码都是相同的,我只能假设它们对于 breakcontinue 来说是完全相同的,特别是因为程序集在每个场景中都使用标签和跳转。

为了确保正确的结果,我在过程中非常细致,还使用了 Visual Studio Code 的比较文件功能。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文