而(1)Vs. for(;;)有速度差吗?

时间:2009-05-20 02:34:09

标签: c++ perl optimization performance

长版......

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

短版......

如果你需要一个无限循环来突破,是否有理由更喜欢while (1)而不是for (;;)

注意:如果问题不清楚。这在几个朋友之间纯粹是一次有趣的学术讨论。我知道这不是一个超级重要的概念,所有程序员都应该为之痛苦。感谢所有伟大的答案我(我相信其他人)从这次讨论中学到了一些东西。

更新:前面提到的同事在下面做出了回应。

这里引用它以防被埋葬。

  

它来自AMD汇编程序员。他说C程序员   (人们)没有意识到他们的代码效率低下。他说   今天虽然,gcc编译器非常好,并且让人们喜欢他   的业务。他举例说,并告诉我while 1 vs   for(;;)。我现在出于习惯而使用gcc,特别是口译员   这几天会做同样的操作(处理器跳转),   因为它们已经过优化。

20 个答案:

答案 0 :(得分:214)

在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

同样在海湾合作委员会中:

#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

所以我猜答案是,它们在许多编译器中都是一样的。当然,对于其他一些编译器,情况可能不一定如此,但是循环内部的代码可能比循环本身要贵几千倍,所以谁在乎呢?

答案 1 :(得分:55)

使用GCC,它们似乎都编译为相同的汇编语言:

L2:
        jmp     L2

答案 2 :(得分:53)

没有太多理由偏爱一个而不是另一个。我认为while(1),特别是while(true)for(;;)更具可读性,但这只是我的偏好。

答案 3 :(得分: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 :(得分:28)

用于发出

警告的Visual C ++编译器
while (1) 

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

for (;;)

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

答案 5 :(得分:26)

如果您想朝那个方向进行优化,

for(;;)可以少输入一个字符。

答案 6 :(得分:20)

使用此旧编译器for(;;)的Turbo C会产生更快的代码while(1)

今天gcc,Visual C(我认为几乎所有)编译器都很好地优化了,很少使用4.7 MHz的CPU。

在那些日子里,for( i=10; i; i-- )for( i=1; i <=10; i++ )快,因为比较i为0,会导致CPU-Zero-Flag条件跳转。并且使用最后一次递减操作( 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

答案 7 :(得分:13)

对于所有人争论你不应该使用indefinte while循环,并建议使用开放 goto 的愚蠢的东西(严重的,哎呀)

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”技巧,我必须使用你的代码,准备一只眼睛打开睡觉,特别是如果你不止一次这样做,因为那种东西会产生可怕的意大利面条代码。

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

答案 8 :(得分:9)

如果编译器没有进行任何优化,for(;;)总是比while(true)快。这是因为while语句每次都会计算条件,但for-statement是无条件跳转。但是如果编译器优化控制流,它可能会生成一些操作码。您可以非常轻松地阅读反汇编代码。

P.S。你可以像这样写一个无限循环:

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

答案 9 :(得分:9)

来自Stroustrup,TC ++ PL(第3版),§6.1.1:

  

好奇的符号for (;;)是指定无限循环的标准方法;你可以发音“永远”。 [...] while (true)是另一种选择。

我更喜欢for (;;)

答案 10 :(得分:8)

我曾经听过这个。

它来自AMD汇编程序员。他表示,C程序员(人员)没有意识到他们的代码效率低下。他今天说,gcc编译器非常好,让像他这样的人破产。他举例说,并告诉我while 1 vs for(;;)。我现在出于习惯而使用它,但是gcc,特别是解释器在这些日子里都会做同样的操作(处理器跳转),因为它们已经过优化。

答案 11 :(得分:5)

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

答案 12 :(得分:3)

我很惊讶没有人在perl中正确测试for (;;)while (1)

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

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

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

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

在Ubuntu 11.04 x86_64上使用perl 5.10.1我得到以下结果:

          Rate   for  for2 while
for   100588/s    --   -0%   -2%
for2  100937/s    0%    --   -1%
while 102147/s    2%    1%    --

while循环显然是这个平台的赢家。

在带有perl 5.14.1的FreeBSD 8.2 x86_64上:

         Rate   for  for2 while
for   53453/s    --   -0%   -2%
for2  53552/s    0%    --   -2%
while 54564/s    2%    2%    --

虽然循环也是胜利者。

在带有perl 5.14.1的FreeBSD 8.2 i386上:

         Rate while   for  for2
while 24311/s    --   -1%   -1%
for   24481/s    1%    --   -1%
for2  24637/s    1%    1%    --

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

我的结论是,如果程序员正在优化速度,那么应该在x86_64平台上使用while循环。显然,在优化空间时应使用for循环。遗憾的是,对于其他平台,我的结果尚无定论。

答案 13 :(得分:2)

总结for (;;) vs while (1)辩论很明显,前者在较旧的非优化编译器时代更快,这就是为什么你倾向于在较旧的代码库中看到它,例如狮子会的Unix源代码评论,然而在badass优化编译器的时代,那些增益被优化掉了,因为后者比前者更容易理解,我相信它会更优选。

答案 14 :(得分:2)

while(1)for(;;)的惯用语,被大多数编译器识别。

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

答案 15 :(得分:2)

我很惊讶没有人提供更直接的形式,对应于所需的装配:

forever:
     do stuff;
     goto forever;

答案 16 :(得分:2)

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

然而,实际上,即使采用“不”优化,编译器仍然会将两者都降低到相同的水平。它们也可能会发出警告,因为它可能表示存在逻辑错误。例如,while的参数可以在其他地方定义,但你没有意识到它是恒定的。

答案 17 :(得分:1)

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

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

根据“ 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对1。 (在某些字体中,它们看起来相同)。

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

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

答案 18 :(得分:-3)

我认为两者在性能方面都是一样的。但我更喜欢(1)为了便于阅读,但我怀疑为什么你需要一个无限循环。

答案 19 :(得分:-14)

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

<小时/> 我的观点暗示但上面没有明确说明,一个体面的编译器会为两个循环形式生成完全相同的代码。更重要的一点是,循环结构是任何算法运行时间的一小部分,您必须首先确保优化了算法以及与之相关的所有其他内容。优化循环结构应该绝对位于优先级列表的底部。