而(1)for(;;)是否存在速度差异?


154

长版...

一位同事在看到我while (1)for (;;)更快的Perl脚本中使用后断言。我认为他们应该是一样的,希望解释器可以优化任何差异。我设置了一个脚本,该脚本将为循环迭代和相同数量的while循环运行1,000,000,000,并记录它们之间的时间。我发现没有明显的区别。我的同事说,一位教授告诉他while (1)正在进行比较1 == 1for (;;)而没有进行比较。我们使用C ++的迭代次数进行了100倍的重复相同的测试,并且差异可以忽略不计。但是,这是一个图形示例,说明了与脚本语言相比,编译后的代码可以提高多少速度。

精简版...

如果您需要无限循环来突破,有什么理由while (1)比a for (;;)优先?

注意:如果问题尚不清楚。这纯粹是几个朋友之间有趣的学术讨论。我知道这不是所有程序员都应该苦恼的超级重要概念。感谢您提供的所有出色答案,我(而且我敢肯定其他人)已经从这次讨论中学到了一些东西。

更新:上述同事在下面进行了回应。

以防万一它被埋在这里。

它来自AMD组装程序员。他说C程序员(人)没有意识到他们的代码效率低下。他今天说,但是,gcc编译器非常好,使像他这样的人破产了。他举例称,并告诉我关于while 1VS for(;;)。我现在已经习惯使用它了,但是由于它们已经过优化,因此gcc尤其是解释器这两天都将执行相同的操作(处理器跳转)。


4
我很好奇。为什么在Perl脚本中需要无限循环?您显然不是在对驱动程序或系统程序进行编程... Infinite很安静:-)
Luc M

125
哪个无限循环最快?大声笑...“我的新电脑是如此之快,它在不到一个小时的时间内运行了无限循环...” ;-)
Arjan Einbu 09年

8
是那个社会学教授告诉过他的吗?在现代时代,您键入的代码并不是计算机最终看到的。
布赖恩·弗伊

5
我希望您测试它所需的时间比知道哪一个更快(如果有的话)要节省的时间长得多。即使您在编程的整个生命周期中都摊销了它。
Peter Recore 2010年

4
为什么编译器会生成代码来执行它知道没有副作用的测试,而编译器已经知道了谁的结果?这是没有意义的。
David Schwartz

Answers:


218

在perl中,它们产生相同的操作码:

$ perl -MO=Concise -e 'for(;;) { print "foo\n" }'
a  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 2 -e:1) v ->3
9     <2> leaveloop vK/2 ->a
3        <{> enterloop(next->8 last->9 redo->4) v ->4
-        <@> lineseq vK ->9
4           <;> nextstate(main 1 -e:1) v ->5
7           <@> print vK ->8
5              <0> pushmark s ->6
6              <$> const[PV "foo\n"] s ->7
8           <0> unstack v ->4
-e syntax OK

$ perl -MO=Concise -e 'while(1) { print "foo\n" }'
a  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 2 -e:1) v ->3
9     <2> leaveloop vK/2 ->a
3        <{> enterloop(next->8 last->9 redo->4) v ->4
-        <@> lineseq vK ->9
4           <;> nextstate(main 1 -e:1) v ->5
7           <@> print vK ->8
5              <0> pushmark s ->6
6              <$> const[PV "foo\n"] s ->7
8           <0> unstack v ->4
-e syntax OK

同样在GCC中:

#include <stdio.h>

void t_while() {
    while(1)
        printf("foo\n");
}

void t_for() {
    for(;;)
        printf("foo\n");
}

    .file   "test.c"
    .section    .rodata
.LC0:
    .string "foo"
    .text
.globl t_while
    .type   t_while, @function
t_while:
.LFB2:
    pushq   %rbp
.LCFI0:
    movq    %rsp, %rbp
.LCFI1:
.L2:
    movl    $.LC0, %edi
    call    puts
    jmp .L2
.LFE2:
    .size   t_while, .-t_while
.globl t_for
    .type   t_for, @function
t_for:
.LFB3:
    pushq   %rbp
.LCFI2:
    movq    %rsp, %rbp
.LCFI3:
.L5:
    movl    $.LC0, %edi
    call    puts
    jmp .L5
.LFE3:
    .size   t_for, .-t_for
    .section    .eh_frame,"a",@progbits
.Lframe1:
    .long   .LECIE1-.LSCIE1
.LSCIE1:
    .long   0x0
    .byte   0x1
    .string "zR"
    .uleb128 0x1
    .sleb128 -8
    .byte   0x10
    .uleb128 0x1
    .byte   0x3
    .byte   0xc
    .uleb128 0x7
    .uleb128 0x8
    .byte   0x90
    .uleb128 0x1
    .align 8
.LECIE1:
.LSFDE1:
    .long   .LEFDE1-.LASFDE1
.LASFDE1:
    .long   .LASFDE1-.Lframe1
    .long   .LFB2
    .long   .LFE2-.LFB2
    .uleb128 0x0
    .byte   0x4
    .long   .LCFI0-.LFB2
    .byte   0xe
    .uleb128 0x10
    .byte   0x86
    .uleb128 0x2
    .byte   0x4
    .long   .LCFI1-.LCFI0
    .byte   0xd
    .uleb128 0x6
    .align 8
.LEFDE1:
.LSFDE3:
    .long   .LEFDE3-.LASFDE3
.LASFDE3:
    .long   .LASFDE3-.Lframe1
    .long   .LFB3
    .long   .LFE3-.LFB3
    .uleb128 0x0
    .byte   0x4
    .long   .LCFI2-.LFB3
    .byte   0xe
    .uleb128 0x10
    .byte   0x86
    .uleb128 0x2
    .byte   0x4
    .long   .LCFI3-.LCFI2
    .byte   0xd
    .uleb128 0x6
    .align 8
.LEFDE3:
    .ident  "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3"
    .section    .note.GNU-stack,"",@progbits

所以我想答案是,它们在许多编译器中都是相同的。当然,对于其他一些编译器来说,不一定是这种情况,但是循环内的代码可能比循环本身要贵数千倍,那么谁在乎呢?


15
尝试使用B :: Deparse,解析一个无限的for循环将返回while循环:P
肯特·弗雷德里克

27
“在perl中,它们产生相同的操作码” ...是的,但是哪个更快?:-)
Tin Man

6
我喜欢gcc用puts()代替printf(),因为只有一个参数,因此不需要格式化-更快,更安全!(gcc还会根据变量参数列表检查格式标记。)
Lee D

@锡人:它们是等效的,因为计算机执行相同的精确操作:P
BlackBear

1
@snap,这不是“完全”错误,只是在关注运行时成本。我无法想象什么样的情况会导致无限循环的解析时间成为决定程序运行速度的关键决定因素
bdonlan


54

没有太多理由比另一个更喜欢一个。我确实认为这while(1)尤其while(true)是比更具可读性for(;;),但这只是我的偏爱。


94
#define EVER ;; 对于(曾经)我一直都觉得这种有趣。
汤姆,

19
#define ever(;;)永远如何?
马丁·科特

16
从表面上看,这两个代码看起来都更具可读性,但是我尽量不要为维护程序员(通常是我)定义新的关键字,以免伤脑筋。
比尔蜥蜴

13
@Martin无效,因为#define不能在令牌内替换,并且 forever而是它自己的令牌。
莉莉·钟

2
“我尽量不要为维护定义新的关键字” –如果只有更多的人采取这种态度,那么我每次转身时都不会被所有这些愚蠢而神奇的狡猾的恶作剧所吸引!
基督

31

根据标准没有差异。6.5.3 / 1具有:

for语句

for ( for-init-statement ; conditionopt ; expressionopt ) statement

相当于

{
  for-init-statement
  while ( condition ) {
    statement
    expression ;
  }
}

而6.5.3 / 2具有:

条件和表达式中的一个或两个都可以省略。缺少条件会使隐式while子句等效于while(true)。

因此,根据C ++标准,代码如下:

for (;;);

与以下内容完全相同:

{
  while (true) {
    ;
    ;
  }
}

4
这根本与生成的代码或性能无关。该标准仅定义功能。当然,性能将是相同的。
Potatoswatter

1
我不认为在性能上的差异确实违反了常规规则。如果是这样,则将不允许编译器根据“如果”规则(例如,通过对独立语句进行重新排序)来加速您的代码。实际上,编译器正是这样做的。但是我的标准副本在楼上。
史蒂夫·杰索普

28

用于发出警告的Visual C ++编译器

while (1) 

(常量表达式),但不适用于

for (;;)

for (;;)由于这个原因,我继续了首选的做法,但是我不知道这些天编译器是否仍然这样做。


该警告可能是因为您使用了while(1)而不是while(true)
jrharshath

16
true是一个常数。而(true)是一个常量表达式。对于感兴趣的任何人,警告C4127记录在这里:msdn.microsoft.com/zh-cn/library/6t66728h
肖恩e

是的,对于1和true都仍然存在警告。这就是为什么我总是使用(;;)
Elviss Strazdins 16/7/11

26

for(;;) 如果您想朝那个方向去优化,则少键入一个字符。


21
很高兴打高尔夫球。否则,选择语法的理由很差。
亚当·贝莱尔

@AdamBellaire Terseness通常会提高可读性,超过一定的技能门槛。
矢量Gorgoth

20

使用这种旧编译器的Turbo C可以for(;;)加快代码的编写while(1)

如今,gcc,Visual C(我认为几乎所有的)编译器都能很好地进行优化,并且很少使用4.7 MHz的CPU。

在那些日子里,a的for( i=10; i; i-- )速度快于for( i=1; i <=10; i++ ),因为compare i为0,因此导致CPU零标志条件跳转。并且使用最后的递减操作对零标记进行了修改( i-- ),不需要额外的cmp操作。

    call    __printf_chk
    decl    %ebx          %ebx=iterator i 
    jnz     .L2
    movl    -4(%ebp), %ebx
    leave

for(i=1; i<=10; i++)带有额外的cmpl:

    call    __printf_chk
    incl    %ebx
    cmpl    $11, %ebx
    jne     .L2
    movl    -4(%ebp), %ebx
    leave

13

对于所有争论的人,您不应该使用indefinte while循环,并建议一些愚蠢的东西,例如使用open goto的东西(严重的是,ouch)

while (1) {
     last if( condition1 );
     code();
     more_code(); 
     last if( condition2 ); 
     even_more_code(); 
}

真的无法通过其他任何方式有效地表示出来。并非没有创建出口变量并进行魔术操作以使其保持同步。

如果您更喜欢goto-esque语法,请使用一些合理的方法来限制范围。

flow: { 

   if ( condition ){ 
      redo flow;
   }
   if ( othercondition ){ 
       redo flow;
   }
   if ( earlyexit ){ 
       last flow;
   }
   something(); # doesn't execute when earlyexit is true 
}

最终速度不是那么重要

担心速度明智的不同循环结构的有效程度是浪费大量时间。过早的优化。我想不出任何情况,在选择循环构造时,分析代码会发现瓶颈。

一般其如何循环和什么样的循环。

您应该“优化”可读性和简洁性,并写出最能说明问题的信息给找到您的代码的下一个可怜的傻瓜。

如果您使用有人提到的“ goto LABEL”技巧,并且我必须使用您的代码,请准备睁开一只眼睛睡觉,尤其是如果您不止一次这样做,因为这种东西会产生 令人讨厌的意大利面条式代码。

仅仅因为您可以创建意大利面条代码并不意味着您应该


9

源自Stroustrup,TC ++ PL(第3版),第6.1.1节:

好奇符号for (;;)是指定无限循环的标准方法。您可以说“永远”。[...] while (true)是一种替代方法。

我更喜欢for (;;)


9

如果编译器不做任何优化,for(;;)总是会比while(true)。这是因为while语句每次都会评估条件,而for语句是无条件跳转。但是,如果编译器优化了控制流程,则可能会生成一些操作码。您可以非常轻松地阅读反汇编代码。

PS,您可以编写一个无限循环,如下所示:

#define EVER ;;
  //...
  for (EVER) {
    //...
  }

在当今时代,永远不应该用EVS(青少年代)代替!严重的是,尽管我只是简单地使用for(;;){}。很久以前,我在网上阅读过有关两者之间差异的信息(当时我还很小,实际上并不知道它们是相同的),只是坚持阅读了我的内容。
Bja

8

我听说过一次

它来自AMD组装程序员。他说,C程序员(人们)没有意识到他们的代码效率低下。他今天说,但是,gcc编译器非常好,使像他这样的人破产了。他举例称,并告诉我关于while 1VS for(;;)。我现在已经习惯了使用它,但是由于它们已经过优化,因此gcc尤其是解释器这两天都将执行相同的操作(处理器跳转)。


5

在优化的编译语言版本中,两者之间应该没有明显的区别。两者都不应最终在运行时执行任何比较,它们只会执行循环代码,直到您手动退出循环(例如使用break)。


3

令我感到惊讶的是,没有人能for (;;)while (1)Perl 进行适当的测试!

因为perl是解释语言,所以运行perl脚本的时间不仅包括执行阶段(在这种情况下是相同的),还包括执行之前的解释阶段。进行速度比较时,必须考虑这两个阶段。

幸运的是,perl有一个方便的Benchmark模块,我们可以使用它来实现一个基准,例如:

#!/usr/bin/perl -w

use Benchmark qw( cmpthese );

sub t_for   { eval 'die; for (;;) { }'; }
sub t_for2  { eval 'die; for (;;)  { }'; }
sub t_while { eval 'die; while (1) { }'; }

cmpthese(-60, { for => \&t_for, for2 => \&t_for2, while => \&t_while });

请注意,我正在测试infinite for循环的两个不同版本:一个版本比while循环短,而另一个版本具有额外的空间以使其长度与while循环相同。

在具有perl 5.10.1的Ubuntu 11.04 x86_64上,我得到以下结果:

          为for2评分
持续100588 / s--0%-2%
for2 100937 / s 0%--1%
而102147 / s 2%1%-

while循环显然是此平台上的赢家。

在带有perl 5.14.1的FreeBSD 8.2 x86_64上:

         为for2评分
持续53453 / s--0%-2%
for2 53552 / s 0%--2%
而54564 / s 2%2%-

虽然循环也是这里的赢家。

在带有perl 5.14.1的FreeBSD 8.2 i386上:

         为for评分
而24311 / s--1%-1%
持续24481 / s 1%--1%
for2 24637 / s 1%1%-

令人惊讶的是,带有额外空间的for循环是这里最快的选择!

我的结论是,如果程序员针对速度进行了优化,则应在x86_64平台上使用while循环。显然,在优化空间时应使用for循环。不幸的是,对于其他平台,我的结果尚无定论。


9
该结论是公然错误的。Benchmark有其局限性,如果结果相差7%以内,则不能用于区分快与慢。而且,您还没有测试forwhile循环之间的差异,因为每个子程序die在到达循环之前都会先进行循环。从什么时候开始,空白量才对Perl解释器重要?对不起,但分析存在极大缺陷。
Zaid

2
@Zaid,谢谢您的评论!您介意发布自己的答案,以便每个人都可以从中学到吗?:) die我的代码中存在,因为我的目的是测试编译时间差。正如其他人已经指出的那样,结果字节码是相同的,因此没有必要进行测试。令人惊讶的是,在这种情况下,在我的测试环境中,空白的数量似乎没有什么区别。这可能与字符最终如何在内存中对齐或其他原因有关……
snap

4
我不需要发布答案,因为bdonlan已经提到了我要说的话。即使您正在比较编译时间,这些数字Benchmark也没有定论。完全不相信有1%的差异!
Zaid

只有60次迭代?运行测试大约5分钟,以便获得更准确的相对时间。
Mooing Duck 2012年

-60运行测试60秒。
捕捉

2

从理论上讲,一个完全天真的编译器可以将文字“ 1”存储在二进制文件中(浪费空间),并检查每次迭代是否1 == 0(浪费时间和更多空间)。

但是,实际上,即使不进行“优化”,编译器仍会将两者简化为相同的形式。他们还可能发出警告,因为它可能指示逻辑错误。例如,的参数while可能在其他地方定义,而您没有意识到它是恒定的。


2

令我惊讶的是,没有人提供与所需组装相对应的更直接形式:

forever:
     do stuff;
     goto forever;

剂量不等于while 1或c中的(;;)的机器代码?
科帕斯2010年

1
这种方法的另一个缺点:它没有将循环封闭在一个块中,因此违反了封装方法-因此,循环中声明的任何变量都可以在循环外部使用。(当然可以{ forever: do stuff; goto forever; }
罗伊·廷克

2

while(1)for(;;)大多数编译器都认可的惯用法。

我很高兴看到perl也认识到until(0)


在什么情况下,直到(0)会有所帮助?
巴拿马杯

3
直到()与while()相反,就像除非()与if()相反。正如建议在此线程中的其他地方一样,可以这样写:do {something ...} while(!condition)一种替代方法可能是直到(condition){something}
JMD

2

总结一下for (;;)vs while (1)辩论,很明显,在较旧的非优化编译器时代,前者速度更快,这就是为什么您倾向于在较旧的代码库(例如Lions Unix源代码评论)中看到它的原因,但是在Badass优化时代编译器将这些收益进行了优化,并结合了后者比前者更易于理解的事实,我认为这将是更可取的。


2

只是碰到了这个线程(尽管已经晚了很多年)。

我想我发现了“ for(;;)”比“ while(1)”更好的实际原因。

根据“ Barr编码标准2018”

Kernighan & Ritchie long ago recommended for (;;) , which has the additional benefit
of insuring against the visually-confusing defect of a while (l); referencing a variable l’.

基本上,这不是速度问题,而是可读性问题。取决于代码的字体/打印方式,一段时间内的数字one(1)可能看起来像小写字母l。

即1对l。(在某些字体中,这些看起来相同)。

因此while(1)可能看起来像一些while循环,取决于变量字母L。

while(true)也可能有效,但在某些较旧的C和嵌入式C情况下,除非包含stdbool.h,否则尚未定义true / false。


2
我想说的是您代码中的问题是您有一个名为的变量l,而不是那个,1并且l看起来很相似。
mjuopperi

同意,我知道Barr编码标准还在其他地方说过,即使在for循环中,变量也必须至少3个字符。即在for循环中没有i ++等。我倾向于认为这可能会很多。在键入时,我还注意到不仅仅是看起来像1的字母L。通常用作变量的字母i也会引起问题。
尼克·劳

-3

我认为两者在性能方面是相同的。但是我更喜欢while(1)的可读性,但是我质疑为什么需要无限循环。


-14

他们是一样的。还有更多重要的问题需要思考。


我在上面隐含但没有明确提出的观点是,一个体面的编译器将为两种循环形式生成完全相同的代码。更大的一点是,循环结构仅占任何算法运行时间的一小部分,您必须首先确保已优化算法以及与之相关的所有其他事物。优化循环结构绝对应该放在优先级列表的底部。


22
没有链接或解释。无助,主观和有点屈尊。
cdmckay

1
好吧,没有证据,但是他是对的。他们都称操作码为false时跳转。(这将使其与goto相同,但没人会喜欢上gotos)
Matthew Whited

3
我没有意识到只有要问的重要问题,我的错误是我的第一个问题。
巴拿马杯

3
是的,我承认这是屈服的。但是,严重的是,即使没有任何证据,也很明显他们将迅速进入同一个球场。如果问题是关于风格,那会有些争论。我试图指出的是,在要担心的事情列表上,这确实应该放在列表的底部。
Mark Ransom

8
我不是想成为一个混蛋。我试图提出一个观点。当我发布它时,我正在尝试一种黑暗的幽默,很明显我失败了。为此我深表歉意。
Mark Ransom
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.