ロングバージョン...
今日、同僚は、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
_ vsfor(;;)
について教えてくれました。今は習慣的に使用していますが、gccと特にインタープリターは最適化されているため、これら両方の日で同じ操作(プロセッサーのジャンプ)を行います。
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
答えは、多くのコンパイラで同じだと思います。もちろん、他の一部のコンパイラーでは必ずしもそうではないかもしれませんが、ループ内のコードがループ自体よりも数千倍も高価になる可能性があります。
GCCを使用すると、どちらも同じアセンブリ言語にコンパイルされるようです。
L2:
jmp L2
一方を他方より好む理由はあまりありません。 while(1)
、特にwhile(true)
はfor(;;)
よりも読みやすいと思いますが、それは私の好みです。
規格による違いはありません。 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) {
;
;
}
}
Visual C++コンパイラは、
_while (1)
_
(定数式)ではなく
_for (;;)
_
その理由でfor (;;)
を好む慣行を続けてきましたが、最近コンパイラーがまだそうしているかどうかはわかりません。
for(;;)
は、物事を最適化するためにその方向に進みたい場合、入力する文字が1つ少なくなります。
この古いコンパイラ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
_
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
コンパイラが最適化を行わない場合、for(;;)
はwhile(true)
よりも常に高速になります。これは、while文が毎回条件を評価するが、for文は無条件のジャンプであるためです。しかし、コンパイラーが制御フローを最適化すると、いくつかのオペコードが生成される場合があります。逆アセンブリコードは非常に簡単に読むことができます。
追伸次のような無限ループを作成できます。
#define EVER ;;
//...
for (EVER) {
//...
}
Stroustrup、TC++ PL(第3版)、§6.1.1から:
奇妙な表記
for (;;)
は、無限ループを指定する標準的な方法です。 「永遠に」発音できます。 [...]while (true)
は代替です。
私はfor (;;)
を好みます。
これについて一度聞いたことがあります。
AMD Assemblyプログラマーから来ました。彼は、Cプログラマー(人々)が自分のコードが非効率であることを認識していないと述べました。しかし彼は今日、gccコンパイラは非常に優れており、彼のような人々を廃業に追い込んでいると言った。たとえば、彼は言って、_while 1
_ vs for(;;)
について教えてくれました。私は今習慣からそれを使用しますが、gccと特にインタープリターは最適化されているため、これらの両方の日で同じ操作(プロセッサーのジャンプ)を行います。
コンパイルされた言語の最適化されたビルドでは、2つの間に大きな違いはありません。どちらも実行時に比較を実行する必要はありません。手動でループを終了するまでループコードを実行します(たとえば、break
を使用)。
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ループを使用する必要があります。私の結果は、残念ながら他のプラットフォームに関して決定的ではありません。
for (;;)
vs while (1)
の議論をまとめると、前者は古い非最適化コンパイラの時代に高速であったことは明らかです。しかし、ライオンズのUnixソースコードの解説として、コンパイラの最適化の時代では、これらの利益は最適化され、後者は前者よりも理解しやすいという事実と結びついています。
while(1)
は、ほとんどのコンパイラで認識されるfor(;;)
のイディオムです。
Perlがuntil(0)
を認識することも嬉しかったです。
希望するアセンブリに対応する、より直接的なフォームを提供している人が誰もいなかったことに驚いています。
forever:
do stuff;
goto forever;
理論的には、完全にナイーブコンパイラは、リテラル '1'をバイナリに保存し(スペースを浪費する)、反復ごとに1 == 0である(時間とスペースを浪費する)かどうかを確認できます。
しかし、実際には、「最適化なし」であっても、コンパイラーは両方を同じに減らします。また、論理エラーを示す可能性があるため、警告を発する場合があります。たとえば、while
の引数は別の場所で定義でき、定数であることに気づきません。
このスレッドに出くわしました(かなり数年遅れていますが)。
「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はまだ定義されていません。