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

时间:2011-03-20 05:22:56

标签: c performance loops goto

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

  1. goto循环

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

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

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

10 个答案:

答案 0 :(得分:16)

一般来说,forwhile循环编译为与goto相同的内容,因此它通常不会产生任何影响。如果你有疑虑,你可以随意尝试这三个,看看哪个需要更长的时间。即使你循环十亿次,你也无法衡量差异。

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

答案 1 :(得分:6)

编写短程序,然后执行此操作:

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

分析输出并查看是否存在任何差异。一定要引入一定程度的不可预测性,这样编译器就不会将程序优化为零。

编译器在优化这些微不足道的问题方面做得很好。我建议不要担心它,而是专注于使你作为程序员更有效率的原因。

速度和效率是一个值得担心的好事,但99%的时间涉及使用正确的数据结构和算法...而不用担心for是否比{{1}更快或者while等等。

答案 2 :(得分:4)

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

就个人而言,我对程序员与史蒂文斯的工作争辩不够好,所以我不会尝试。 goto 可以对与性能相关的问题有用,但 的限制是非常严格的。

答案 3 :(得分:3)

可能是编译器,优化器和特定于体系结构。

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

最后,差异可能是微小的,只有在需要大量迭代时才有意义,回答这个问题的方法是为特定目标和编译器(以及编译器设置)构建代码。兴趣,并检查生成的机器级代码或直接测量执行时间。

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

答案 4 :(得分:2)

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

你可以通过使用goto获得一些东西,但通常只有当你正在生成一个普通循环根本不能(至少干净)的控制流时。一个典型的例子是跳到循环的中间来获得一个循环和一个半结构,大多数语言的正常循环语句(包括C)都不能干净地提供。

答案 5 :(得分:2)

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

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

答案 6 :(得分:1)

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

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

答案 7 :(得分:0)

有一些利基垂直,其中goto仍然常用作标准练习,由一些非常非常聪明的人在这些设置中没有对goto的偏见。我曾经在一家以模拟为重点的公司工作,所有当地的fortran代码都有很多东西,团队非常聪明,而且软件工作接近完美。

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

除了通常将小循环扩展为无循环之外,所有条件分支在流水线处理器体系结构中被认为是“采用”(真实),直到解码阶段。因此,根据Harper的观点,goto在简单的循环控制中具有任何优势是不现实的(正如同时或彼此没有优势一样)。当将goto检查的附加条件添加到嵌套循环的EACH或嵌套ifs是次优的时候,GOTO通常在多个嵌套循环或多个嵌套ifs中有意义。

在简单循环中优化搜索类型的操作时,使用sentinal有时比其他任何方法更有效。本质上,通过在数组的末尾添加一个虚拟值,您可以避免检查两个条件(数组的结尾和找到的值)只是一个条件(找到的值),并在内部保存cmp操作。我不知道编译器是否会自动执行此操作。

答案 8 :(得分:0)

转到循环:

decimal(10, 1)

用于循环:

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

end_Chicken:
x = 0;

while循环:

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

}

Image from results

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

答案 9 :(得分:0)

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

结论/TL;底部的DR。


首先,我比较了 label:gotodo {} while。您不能将 for () {} 循环与 this 进行比较(真诚地),因为 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 () {},每个编译器,只有 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

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:goto 的任何可能的等效安排之间似乎没有任何区别、do {} whilewhile {}for () {},至少在使用 g++ 9.3.0 和 clang++ 10.0.0 的 Linux 上是这样。

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

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