代码之家  ›  专栏  ›  技术社区  ›  Copas

(1)和(;;)之间有速度差吗?

  •  145
  • Copas  · 技术社区  · 17 年前

    长版…

    一位同事在看到我使用 while (1) 在Perl脚本中, for (;;) 更快。我认为他们应该是一样的,希望口译员能消除任何分歧。我设置了一个脚本,它将运行100000000次循环迭代和相同数量的while循环,并记录两次循环之间的时间。我看不出有什么区别。我的同事说一位教授告诉他 (1) 正在做比较 1 == 1 以及 (;) 不是。我们用100X重复了同样的测试,用C++的迭代次数,差异是可以忽略不计的。然而,这是一个图形化的例子,说明编译后的代码与脚本语言相比可以快得多。

    短版本…

    有什么理由喜欢 (1) 过了 (;) 如果你需要一个无限循环来突破?

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

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

    这里引用以防被埋。

    它来自一个AMD组装程序设计人员。他说C程序员 (人们)没有意识到他们的代码效率低下。他说 不过,今天GCC的编译程序非常好,可以让像他这样的人退出。 做生意。例如,他说,并告诉我 while 1 VS for(;;) . 我现在使用它是出于习惯,但GCC,尤其是口译员 这两天将执行相同的操作(处理器跳转)。 因为它们被优化了。

    20 回复  |  直到 7 年前
        1
  •  212
  •   bdonlan    17 年前

    在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
    

    所以我想答案是,在许多编译器中它们是相同的。当然,对于其他一些编译器来说,情况未必如此,但很可能循环内部的代码比循环本身贵几千倍,那么谁在乎呢?

        2
  •  55
  •   Martin Cote    17 年前

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

    L2:
            jmp     L2
    
        3
  •  52
  •   Bill the Lizard    17 年前

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

        4
  •  31
  •   Chris Jefferson    11 年前

    根据标准没有区别。65.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) {
        ;
        ;
      }
    }
    
        5
  •  28
  •   osgx    15 年前

    用于发出警告的VisualC++编译器

    while (1) 
    

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

    for (;;)
    

    我继续练习优先 for (;;) 因为这个原因,但我不知道编译器现在是否仍然这样做。

        6
  •  26
  •   osgx    15 年前

    for(;;) 如果你想朝那个方向去优化,就少输入一个字符。

        7
  •  20
  •   chue x    12 年前

    带这个老编译器的turbo C for(;;) 从而产生更快的代码 while(1) .

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

    那时候 for( i=10; i; i-- ) 比快 for( i=1; i <=10; i++ ) ,因为比较 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
    
        8
  •  13
  •   Kent Fredric    17 年前

    对于所有的人来说,你不应该使用indefinde while循环,并且建议使用诸如open之类的愚蠢的东西。 古托 S(说真的,哎呀)

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

    不能以任何其他方式有效地表示。不需要创建一个出口变量并执行黑色魔法来保持同步。

    如果您喜欢更哥特式的语法,请使用一些限制范围的理智的方法。

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

    最终速度不是那么重要

    担心不同的循环结构在速度方面有多有效,这是浪费时间。通过和通过过早优化。我想不出我见过的任何情况,在我选择循环结构时,分析代码发现了瓶颈。

    一般来说, 怎样 循环和 什么 循环的

    你应该对可读性和简洁性进行“优化”,然后写任何最能解释问题的东西给下一个找到你的代码的可怜虫。

    如果你使用了别人提到的“goto-label”技巧,而我必须使用你的代码,那么就准备睁一只眼睡觉,特别是如果你不止一次这样做,因为这类东西会 可怕地 意大利面条代码。

    只是因为你 可以 创建意粉代码并不意味着你 应该

        9
  •  8
  •   Hans W    16 年前

    来自stroustrup,tc++pl(第三版),_§6.1.1:

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

    我更喜欢 (;) .

        10
  •  8
  •   chue x    12 年前

    我听说过一次。

    它来自一个AMD组装程序设计人员。他说C程序员(人们)没有意识到他们的代码效率低下。不过,他今天说,GCC的编译器非常好,让像他这样的人失业。例如,他说,并告诉我 while 1 VS for(;;) . 我现在使用它是出于习惯,但GCC和特别是口译员将在这两天做相同的操作(处理器跳转),因为它们是优化的。

        11
  •  8
  •   chue x    12 年前

    如果编译器不做任何优化, for(;;) 总是比 while(true) . 这是因为while语句每次都计算条件,而for语句则是无条件跳转。但是,如果编译器优化了控制流,它可能会生成一些操作码。您可以很容易地读取反汇编代码。

    另外,你可以写一个这样的无限循环:

    #define EVER ;;
      //...
      for (EVER) {
        //...
      }
    
        12
  •  5
  •   Charlie    17 年前

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

        13
  •  2
  •   Nick T twasbrillig    16 年前

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

    然而,在现实中,即使有了“无”优化,编译器仍然会将两者减少到相同的程度。它们还可能发出警告,因为它可能指示逻辑错误。例如, while 可以在其他地方定义,但您没有意识到它是常量。

        14
  •  2
  •   Phil Miller    16 年前

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

    forever:
         do stuff;
         goto forever;
    
        15
  •  2
  •   snap    15 年前

    我很惊讶没有人经过适当的测试 for (;;) 对战 while (1) 在Perl!

    因为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循环长度相同。

    在带有Perl5.10.1的Ubuntu11.04 x86_64上,我得到了以下结果:

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

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

    在FreeBSD 8.2上,使用Perl 5.14.1的x86_64:

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

    而Loop也是这里的赢家。

    在Freebsd 8.2 i386和Perl 5.14.1上:

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

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

    我的结论是,如果程序员正在优化速度,那么while循环应该在x86_平台上使用。显然,在优化空间时应该使用for循环。不幸的是,对于其他平台,我的结果还没有定论。

        16
  •  2
  •   chue x    12 年前

    while(1) 是一个成语 for(;;) 它被大多数编译器识别。

    我很高兴看到Perl认识到 until(0) 也是。

        17
  •  2
  •   redbandit    10 年前

    总结一下 for (;;) VS while (1) 很明显,前者在老的非优化编译器时代速度更快,这就是为什么你倾向于在老的代码库中看到它,如Lions Unix源代码注释,但是在糟糕的优化编译器时代,这些收益被优化了,这与后者比fo更容易理解的事实相耦合。我相信这样会更好。

        18
  •  1
  •   Nick Law    8 年前

    刚刚遇到这条线(虽然晚了好几年)。

    我想我找到了“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’.
    

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

    即1与L(在某些字体中,这些看起来是相同的)。

    所以while(1)可能看起来像一些依赖于变量字母l的while循环。

    虽然(true)也可以工作,但在一些较旧的C和嵌入式C情况下,除非包含stdbool.h,否则尚未定义true/false。

        19
  •  -3
  •   bichonfrise74    17 年前

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

        20
  •  -13
  •   E_net4 Tunn    7 年前

    它们是一样的。还有许多更重要的问题需要考虑。


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