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


154

ロングバージョン...

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

短縮版...

抜け出すために無限ループが必要な場合に、while (1)よりも優先する理由はありますfor (;;)か?

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

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

埋もれる場合に備えて、ここに引用されています。

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


4
私は興味がある。なぜperlスクリプトで無限ループが必要なのですか?あなたは明らかにドライバーやシステムのことをプログラミングしていません... Infiniteは長く静かです:-)
Luc M

125
どの無限ループが最速ですか?笑...「私の新しいコンピュータは非常に高速で、1時間足らずで無限ループを実行します...」;-)
Arjan Einbu 2009年

8
それを社会学の教授に言ったのですか?現代では、入力するコードはコンピューターが最終的に表示するコードとは異なります。
ブライアン・ド・フォイ2009年

5
これをテストするのにかかった時間は、どちらが速いかを知ることで節約できる可能性のある時間よりもはるかに長いと思います。プログラミングの両方の生涯にわたってそれを償却しても。
Peter Recore

4
コンパイラが、副作用がなく、コンパイラがすでに知っている結果であることがわかっているテストを実行するコードを生成するのはなぜですか?それは意味がありません。
David Schwartz

回答:


218

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

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


15
B :: Deparseで試してください。無限forループを逆解析すると、whileループが返されます:P
ケントフレドリック

27
「perlでは、それらは同じオペコードになります」...はい、しかしどちらが速いですか?:-)
ブリキの男

6
引数が1つしかないため、フォーマットするものが何もないため、gccがputs()をprintf()に置き換えたことを気に入っています-より速く、より安全です!(gccはまた、可変引数リストに対してフォーマットタグをチェックします。)
Lee D

@The Tin Man:コンピュータはまったく同じ操作を行うため、これらは同等です:P
BlackBear

1
@snap、それは「完全に」不正確ではなく、単に実行時のコストに焦点を合わせています。私は一種の状況がもたらすであろうか想像することはできませんの解析時間無限ループがどのくらいの速あなたのプログラムの実行中に係数を決定する重要なもの
bdonlan

55

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

L2:
        jmp     L2

20
-SオプションでGCCを使用する(アセンブル、リンクしない)
Martin Cote

54

どちらか一方を優先する理由はそれほど多くありません。私はそれがwhile(1)特にwhile(true)より読みやすいと思いますがfor(;;)、それは私の好みです。


94
#define EVER ;; for(EVER)私はいつもそのような面白さを見つけました。
トム

19
#define ever(;;)forever;
マーティンコート

16
どちらも表面上は読みやすいように見えますが、メンテナンスプログラマ(通常は私)が新しいキーワードを定義して、頭を悩ませるようなことはしません。
リザードを請求する

13
@Martinは機能しません。#defineはトークン内で置き換えられず、foreverそれ自体がトークンであるためです。
Lily Chung

2
「私は私のメンテナンスのために新しいキーワードを定義しないように努めます」—より多くの人々がその態度を取ったとしたら、私が向きを変えるたびに、これらの非常識で不思議な手振りの悪魔をまったくつかまないでしょう!
tchrist

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
これは、生成されたコードやパフォーマンスにはまったく関係ありません。この規格は機能のみを定義しています。もちろん、パフォーマンスは同じです。
Potatoswatter 2009

1
パフォーマンスの違いがas-ifルールに違反しているのは本当だとは思いません。そうである場合、コンパイラーは、as-ifルールの下で、例えば独立したステートメントを再配列することにより、コードを高速化することを許可されません。コンパイラは実際にそれを正確に行います。しかし、私の標準のコピーは2階です。
スティーブジェソップ

28

警告を出力するために使用されるVisual C ++コンパイラ

while (1) 

(定数式)ただし、

for (;;)

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


警告は、while(true)ではなくwhile(1)を使用したためと考えられます
jrharshath 2009年

16
trueは定数です。while(true)は定数式です。興味のある方のために、警告C4127をここに​​文書化しています:msdn.microsoft.com/en-us/library/6t66728h(
sean e

はい、警告はまだ1とtrueの両方に存在しています。それが、私が常に(;;)を使用する理由です
Elviss Strazdins

26

for(;;) 物事を最適化するためにその方向に行きたい場合、タイプする文字が1つ少なくなります。


21
ゴルフについて知っておくと良い。そうでなければ、構文を選択するための悪い理由。
アダムベレア

@AdamBellaire Tersenessは、特定のスキルのしきい値を超えて、読みやすさを向上させることがよくあります。
Vector Gorgoth 2013年

20

この古いコンパイラfor(;;)を備えたターボCを使用すると、コードが高速になりますwhile(1)

今日のgcc、Visual C(ほとんどすべての場合)コンパイラーは適切に最適化され、4.7 MHzのCPUはほとんど使用されません。

当時、a 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

13

indefinte whileループを使用してはならないと主張し、open goto(真剣にouch)を使用するなどのdaftのものを提案するすべての人にとって

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 
}

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

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

一般的には、ループの方法とループの内容です。

読みやすさと簡潔さのために「最適化」し、コードを見つけた次の貧しい吸盤に問題を説明するのに最適なものは何でも書く必要があります。

あなたが誰かが言及した「後藤LABEL」トリックを使用して、私はあなたのコードを使用する必要がある場合のもののその種が作成されますので、あなたが複数回にそれを行う場合は特に、片目オープンで、スリープ状態に調製することが恐ろしくスパゲティコードを。

ちょうどあなたがいるのでできスパゲッティコードを作成することは、あなたが意味するものではありませんべき


9

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

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

私は好むfor (;;)


9

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

PSあなたはこのような無限ループを書くことができました:

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

現代では、年齢をEVS(10代の話す)に置き換えることはできません。真剣に私は単にfor(;;){}を使用しますが。私はずっと前に2つの違い(私が若い頃、実際には同じであることを知らなかったとき)についてオンラインで読み、読んだ内容にこだわっていました。
Bja 2017年

8

一度聞いたことがあります。

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


5

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


3

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

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

Ubuntu 11.04 x86_64とperl 5.10.1では、次の結果が得られます。

          for2 whileのレート
100588 /秒の場合--0%-2%
for2 100937 / s 0%--1%
一方102147 / s 2%1%-

whileループは明らかにこのプラットフォームの勝者です。

FreeBSD 8.2 x86_64とperl 5.14.1:

         for2 whileのレート
53453 /秒の場合--0%-2%
for 2 53552 /秒0%--2%
一方、54564 /秒2%2%-

ここでもループが勝者です。

FreeBSD 8.2 i386とperl 5.14.1:

         for2の評価中
一方、2311 / s--1%-1%
24481 / sの場合1%--1%
for2 24637 / s 1%1%-

驚いたことに、余分なスペースがあるforループは、ここで最も速い選択です!

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


9
結論は明らかに間違っています。Benchmarkには制限があり、結果が互いに7%以内の場合、高速と低速を区別するために使用することはできません。さらに、各サブはループ自体に到達する前にループするためforwhileループとループの違いをテストしていませんdie。そして、いつPerlインタープリターにとって空白の量が問題になったのですか?申し訳ありませんが、分析には非常に欠陥があります。
Zaid

2
@ザイド、あなたのコメントをありがとう!皆さんがそれから学ぶことができるように、あなた自身の答えを投稿していただけませんか?:) die私の意図はコンパイル時間の違いだけをテストすることなので、コードにあります。他の人が結果のバイトコードは同じであることをすでに指摘しているので、テストしても意味がありません。驚いたことに、私のテスト環境では、この場合、空白の量が少し違うようです。これは、キャラクターがメモリ内で整列する方法などに関係している可能性があります...
snap

4
私が言うことはすでにbdonlanによって言及されているので、私は答えを投稿する必要はありません。また、コンパイル時間を比較している場合でも、数値Benchmarkは決定的ではありません。その1%の違いをまったく信用しないでください!
Zaid

たった60回の反復?より正確な相対時間を取得するために、5分程度のテストを実行します。
Mooing Duck、

-60テストを60秒間実行します。
2012年

2

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

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


2

希望するアセンブリに対応する、より直接的な形式を提供している人がいないことに驚きます。

forever:
     do stuff;
     goto forever;

1と同じマシンコードで終わるわけではないのですか?
Copas 2010

1
このアプローチのもう1つの欠点は、ループをブロックで囲まないことでカプセル化に違反することです。したがって、ループ内で宣言された変数は、ループの外側で使用できます。(もちろん、できます{ forever: do stuff; goto forever; }
ロイ・ティンカー

2

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

perlも認識できるのを見てうれしかったuntil(0)です。


until(0)はどのような状況で役に立ちますか?
コパス2009年

3
until()はwhile()の反対であり、unless()はif()の反対です。このスレッドの別の場所で提案されているように、次のように書くことができます:do {something ...} while(!condition)代替案は、until(condition){something}
JMD

2

for (;;)vsのwhile (1)議論を要約すると、前者が古い非最適化コンパイラーの時代の方が速かったことは明らかです。そのため、Lions Unix Sourceコードの解説などの古いコードベースで見られる傾向がありますが、badass最適化の時代です。コンパイラーは、これらのゲインを最適化することにより、後者を前者よりも理解しやすいという事実と組み合わせて最適化します。


2

このスレッドに遭遇したばかりです(かなり数年遅れていますが)。

「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はまだ定義されていません。


2
私はあなたのコード内の問題は、あなたがという名前の変数があることだろうと言うでしょうl、ではないことを1してl同じように見えることができます。
mjuopperi 2018

同意しますが、Barrコーディング標準では、forループでも変数は少なくとも3文字でなければならないという他の場所での記述も知っています。つまり、forループにi ++などはありません。私はそれが少し多いかもしれないと思う傾向があります。入力中、1のように見えるのはLだけではないことにも気付きました。変数として一般的に使用されるiも問題を引き起こす可能性があります。
Nick Law

-3

性能的にはどちらも同じだと思います。しかし、私は読みやすさのためにwhile(1)を好みますが、なぜ無限ループが必要なのか疑問に思います。


-14

彼らは同じです。熟考するためのはるかに重要な質問があります。


暗黙のうちに明示されていないが、まともなコンパイラーが両方のループ形式に対してまったく同じコードを生成するという点です。さらに重要な点は、ループ構造はアルゴリズムの実行時間のマイナーな部分であり、最初にアルゴリズムとそれに関連する他のすべてを最適化したことを確認する必要があります。ループ構造の最適化は、絶対に優先順位リストの一番下にある必要があります。


22
リンクや説明はありません。役に立たず、主観的で、少し腹を立てています。
cdmckay 2009年

1
確かに証拠はありませんが、彼は正しいです。彼らは両方とも偽のときにジャンプするためにオペコードを呼び出します。(gotoと同じになりますが、gotosは好きではありません)
Matthew Whited

3
重要な質問だけがどこにあるかわからなかったので、私の間違いが最初の質問でした。
コパス2009年

3
はい、私はそれが見下していることを認めます。しかし真剣に、証拠がなくても、彼らがスピード的に同じ球場にいることは明らかです。質問がスタイルに関するものである場合、何かについて議論する必要があります。私は気になるもののリストで、これは本当にリストの一番下にあるべきだと指摘しようとしていました。
マークランサム

8
私はジャークになるつもりはありませんでした。私はポイントを作ろうとしていました。それを投稿したとき、私は一種の暗いユーモアを求めていました、そして私が失敗したことは明らかです。そのため、お詫び申し上げます。
Mark Ransom、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.