而(1)和(; ;)有速度差吗?

长话短说。

一位同事今天在看到我在 Perl 脚本中使用 while (1)之后断言,for (;;)更快。我认为,他们应该同样希望译员能够最大限度地消除任何差异。我设置了一个脚本,它将运行1,000,000,000次循环迭代和相同数量的 while 循环,并记录两次循环之间的时间。我找不到明显的差别。我的同事说,一位教授告诉他,while (1)正在进行 1 == 1for (;;)的比较。我们用100倍的 C + + 迭代次数重复了同样的测试,结果差异可以忽略不计。然而,这是一个生动的例子,说明了编译代码相对于脚本语言的速度有多快。

长话短说。

如果你需要打破一个无限循环,有什么理由选择 while (1)而不是 for (;;)吗?

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

更新: 上述同事以下面的回应加入了讨论。

这里引用以防它被掩埋。

它来自一个 AMD 汇编程序员。他说,C 程序员 (人们)没有意识到他们的代码是低效的 但是现在,gcc 编译器非常好,把像他这样的人排除在外 他说,例如,并告诉我关于 while 1 vs 我现在使用它是出于习惯,但 gcc,特别是口译员 将在这两天执行相同的操作(处理器跳转) , 因为它们被优化了。

60756 次浏览

没有什么理由喜欢一个胜过另一个。我确实认为 while(1),特别是 while(true)for(;;)更具可读性,但这只是我的偏好。

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


我想说的是,一个像样的编译器会为两种循环形式生成完全相同的代码。更重要的一点是,循环结构只是任何算法运行时的一小部分,您必须首先确保已经优化了算法以及与之相关的所有内容。优化循环结构绝对应该在优先级列表的底部。

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

如果你想往那个方向优化,for(;;)就少了一个字符。

通过使用 GCC,它们似乎都可以编译成相同的汇编语言:

L2:
jmp     L2

在 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

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

VisualC + + 编译器用于发出

while (1)

(常量表达式)但不是为了

for (;;)

出于这个原因,我继续使用 for (;;),但我不知道编译器现在是否仍然这样做。

对于所有争论你不应该使用不确定 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”技巧,而我不得不使用你的代码,请做好睁着一只眼睛睡觉的准备,特别是如果你不止一次这样做,因为这种东西会创建 非常可怕意大利面条代码。

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

根据标准没有区别。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) {
;
;
}
}

我认为两者在性能方面是一样的。但是我更喜欢 while (1)的可读性,但是我想知道为什么需要一个无限循环。

while(1)是大多数编译器都能识别的 for(;;)的惯用语。

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

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

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

我更喜欢 for (;;)

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

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

我感到惊讶的是,没有人提供更直接的形式,对应于所需的集合:

forever:
do stuff;
goto forever;

我听说过一次。

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

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

由于 perl 是直译语言的,运行 perl 脚本的时间不仅包括执行阶段(在本例中是相同的) ,还包括执行前的解释阶段。在进行速度比较时,必须考虑到这两个阶段。

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

#!/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 循环显然是这个平台上的赢家。

在 FreeBSD 8.2 x86 _ 64和 perl 5.14.1上:

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 _ 64平台上使用。显然,在优化空间时应该使用 for 循环。不幸的是,我的结果对于其他平台来说是不确定的。

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

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

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

Turbo C 与这个老编译器 for(;;)结果在更快的代码比 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-- )对 Zero-Flag 进行了修改,不需要额外的 cmp 操作。

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

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

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

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

只是偶然发现了这条线索(尽管已经晚了好几年)。

我想我找到了“ 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(1)可能看起来像小写字母 l。

即1对1(在某些字体中看起来是一样的)。

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

While (true)也可以工作,但是在一些较老的 C 和嵌入式 C 中,除非包含 stdbool.h,否则 true/false 还没有定义。