我知道,每个人都讨厌GOTO,没有人推荐它。但那不是重点。我只想知道,哪个代码最快:
goto
循环
int i=3;
loop:
printf("something");
if(--i) goto loop;
while
循环
int i=3;
while(i--) {
printf("something");
}
for
循环
for(int i=3; i; i--) {
printf("something");
}
答案 0 :(得分:16)
一般来说,for
和while
循环编译为与goto
相同的内容,因此它通常不会产生任何影响。如果你有疑虑,你可以随意尝试这三个,看看哪个需要更长的时间。即使你循环十亿次,你也无法衡量差异。
如果查看this answer,您会看到编译器可以为for
,while
和goto
生成完全相同的代码(仅在这种情况下)没有条件)。
答案 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)
只要您生成与普通循环相同的控制流,几乎任何体面的编译器都可以生成相同的代码,无论您使用for
,while
等等。它
你可以通过使用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++)
{
}
在任何情况下使用更多混合测试的循环虽然具有最小的效果,但效果仍然更好。
答案 9 :(得分:0)
在 Linux 上,我使用 g++ 和 clang++ 将下面的代码编译成程序集。有关我如何做到这一点的更多信息,请参阅 here。 (简短版本:g++ -S -O3 filename.cpp
clang++ -S -O3 filename.cpp
,以及您将在下面看到的一些程序集注释以帮助我。)
结论/TL;底部的DR。
首先,我比较了 label:
和 goto
与 do {} 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:
和 goto
与 while {}
与 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 {} while
、while {}
和 for () {}
,至少在使用 g++ 9.3.0 和 clang++ 10.0.0 的 Linux 上是这样。
请注意,我没有在这里测试 break
和 continue
;然而,鉴于在任何情况下为 4 个中的每一个生成的汇编代码都是相同的,我只能假设它们对于 break
和 continue
完全相同,特别是因为程序集使用每个场景的标签和跳转。
为了确保正确的结果,我在我的过程中非常细致,并且还使用了 Visual Studio Code 的比较文件功能。