web-dev-qa-db-ja.com

(1)対for(;;)速度に違いはありますか?

ロングバージョン...

今日、同僚は、Perlスクリプトでwhile (1)を使用したのを見て、for (;;)の方が高速であると断言しました。私は、インタープリターが違いを最適化することを期待して、彼らは同じであるべきだと主張しました。ループの繰り返しと同じ数のwhileループで1,000,000,000を実行するスクリプトを設定し、その間の時間を記録します。目に見える違いは見つかりませんでした。同僚は、教授が、while (1)が_1 == 1_を比較しており、for (;;)は比較していないと言っていたと言いました。 C++で100倍の反復回数で同じテストを繰り返しましたが、その差は無視できました。ただし、これは、コンパイルされたコードがスクリプト言語よりもどれだけ高速であるかを示すグラフィック例でした。

短縮版...

脱出する無限ループが必要な場合、while (1)よりもfor (;;)を好む理由はありますか?

注:質問から明らかでない場合。これは、純粋に楽しい友人同士の学術的な議論でした。これは、すべてのプログラマーが苦しむべき超重要な概念ではないことを認識しています。この議論から私(そして他の人たちも)がいくつかのことを学んだというすばらしい答えをありがとう。

更新:前述の同僚は、以下の回答を検討しました。

埋められた場合に備えてここに引用されています。

AMD Assemblyプログラマーから来ました。彼は、Cプログラマー(人々)が自分のコードが非効率であることを認識していないと述べました。しかし彼は今日、gccコンパイラは非常に優れており、彼のような人々を廃業に追い込んだと述べました。たとえば、彼は言って、_while 1_ vs for(;;)について教えてくれました。今は習慣的に使用していますが、gccと特にインタープリターは最適化されているため、これら両方の日で同じ操作(プロセッサーのジャンプ)を行います。

151
Copas

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

答えは、多くのコンパイラで同じだと思います。もちろん、他の一部のコンパイラーでは必ずしもそうではないかもしれませんが、ループ内のコードがループ自体よりも数千倍も高価になる可能性があります。

215
bdonlan

GCCを使用すると、どちらも同じアセンブリ言語にコンパイルされるようです。

L2:
        jmp     L2
55
Martin Cote

一方を他方より好む理由はあまりありません。 while(1)、特にwhile(true)for(;;)よりも読みやすいと思いますが、それは私の好みです。

53
Bill the Lizard

規格による違いはありません。 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) {
    ;
    ;
  }
}
31
Richard Corden

Visual C++コンパイラは、

_while (1) 
_

(定数式)ではなく

_for (;;)
_

その理由でfor (;;)を好む慣行を続けてきましたが、最近コンパイラーがまだそうしているかどうかはわかりません。

28
sean e

for(;;)は、物事を最適化するためにその方向に進みたい場合、入力する文字が1つ少なくなります。

26
Chris Bartow

この古いコンパイラ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条件付きジャンプが発生するためです。また、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
_
20
Lutz L.

Indefinte whileループを使用すべきではないと主張し、opengoto '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 
}

最終的に速度はそれほど重要ではありません

異なる速度のループ構成がいかに効果的であるかを心配することは、膨大な時間の浪費です。時期尚早の最適化。ループ構造の選択でコードのプロファイリングがボトルネックを見つけた状況は考えられません。

一般に、ループのhowおよびループのwhat

読みやすさと簡潔さを「最適化」し、コードを見つけた次の貧しい吸盤に問題を説明するのに最適なものを書いてください。

誰かが言及した「goto LABEL」トリックを使用し、あなたのコードを使用する必要がある場合、特に複数回行う場合は、片目を開いて眠る準備をしてください。その種のものはhorrificallyスパゲッティコード。

canスパゲッティコードを作成したからといって、should

13
Kent Fredric

コンパイラが最適化を行わない場合、for(;;)while(true)よりも常に高速になります。これは、while文が毎回条件を評価するが、for文は無条件のジャンプであるためです。しかし、コンパイラーが制御フローを最適化すると、いくつかのオペコードが生成される場合があります。逆アセンブリコードは非常に簡単に読むことができます。

追伸次のような無限ループを作成できます。

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

Stroustrup、TC++ PL(第3版)、§6.1.1から:

奇妙な表記for (;;)は、無限ループを指定する標準的な方法です。 「永遠に」発音できます。 [...] while (true)は代替です。

私はfor (;;)を好みます。

9
Hans W

これについて一度聞いたことがあります。

AMD Assemblyプログラマーから来ました。彼は、Cプログラマー(人々)が自分のコードが非効率であることを認識していないと述べました。しかし彼は今日、gccコンパイラは非常に優れており、彼のような人々を廃業に追い込んでいると言った。たとえば、彼は言って、_while 1_ vs for(;;)について教えてくれました。私は今習慣からそれを使用しますが、gccと特にインタープリターは最適化されているため、これらの両方の日で同じ操作(プロセッサーのジャンプ)を行います。

8
Jimmie Clark

コンパイルされた言語の最適化されたビルドでは、2つの間に大きな違いはありません。どちらも実行時に比較を実行する必要はありません。手動でループを終了するまでループコードを実行します(たとえば、breakを使用)。

5
Charlie

Perlでfor (;;)while (1)を適切にテストした人がいないことに驚いています!

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 });

無限forループの2つの異なるバージョンをテストしていることに注意してください。1つはwhileループより短く、もう1つはwhileループと同じ長さにするための余分なスペースがあります。

Perl 5.10.1を使用するUbuntu 11.04 x86_64では、次の結果が得られます。

 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の場合:

 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の場合:

 for2 
 while 24311/sの間のレート--1%-1%
 for 24481/s 1%--1%
 for2 24637/s 1%1%-

驚くべきことに、ここでは余分なスペースのあるforループが最速の選択です!

私の結論は、プログラマが速度を最適化する場合、whileループをx86_64プラットフォームで使用する必要があるということです。スペースを最適化する場合、明らかにforループを使用する必要があります。私の結果は、残念ながら他のプラットフォームに関して決定的ではありません。

3
snap

for (;;) vs while (1)の議論をまとめると、前者は古い非最適化コンパイラの時代に高速であったことは明らかです。しかし、ライオンズのUnixソースコードの解説として、コンパイラの最適化の時代では、これらの利益は最適化され、後者は前者よりも理解しやすいという事実と結びついています。

2
redbandit

while(1)は、ほとんどのコンパイラで認識されるfor(;;)のイディオムです。

Perlがuntil(0)を認識することも嬉しかったです。

2
J M D

希望するアセンブリに対応する、より直接的なフォームを提供している人が誰もいなかったことに驚いています。

forever:
     do stuff;
     goto forever;
2
Phil Miller

理論的には、完全にナイーブコンパイラは、リテラル '1'をバイナリに保存し(スペースを浪費する)、反復ごとに1 == 0である(時間とスペースを浪費する)かどうかを確認できます。

しかし、実際には、「最適化なし」であっても、コンパイラーは両方を同じに減らします。また、論理エラーを示す可能性があるため、警告を発する場合があります。たとえば、whileの引数は別の場所で定義でき、定数であることに気づきません。

2
Nick T

このスレッドに出くわしました(かなり数年遅れていますが)。

「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対l。 (一部のフォントではこれらは同一に見えます)。

したがって、while(1)は変数文字Lに依存するwhileループのように見える場合があります。

while(true)も動作しますが、一部の古いCおよび組み込みCの場合、stdbool.hが含まれていない限り、true/falseはまだ定義されていません。

1
Nick Law