それとも、セマンティクスがすべてですか?
16 に答える
簡単な答え:いいえ、まったく同じです。
理論的にはコンパイラに依存する可能性があると思います。本当に壊れたものは少し違うことをするかもしれませんが、私は驚きます。
楽しみのために、Ubuntuに同梱されているx86gccバージョン4.3.3を使用してまったく同じアセンブリコードにコンパイルされる2つのバリアントがあります。Linuxのobjdumpを使用して、最終的なバイナリで生成されたアセンブリを確認できます。
int main() {{ #if 1 int i = 10; do {printf( "%d \ n"、i); } while(-i); #そうしないと int i = 10; for(; i; --i)printf( "%d \ n"、i); #endif }
編集:これは「オレンジとオレンジ」のwhileループの例で、これも同じものにコンパイルされます。
while(i){printf( "%d \ n"、i); - 私; }
forループとwhileループが同じことを行う場合、コンパイラによって生成されるマシンコードは(ほぼ)同じである必要があります。
たとえば、私が数年前に行ったいくつかのテストでは、
for (int i = 0; i < 10; i++)
{
...
}
と
int i = 0;
do
{
...
i++;
}
while (i < 10);
まったく同じコードを生成するか、(そしてコメントでニールが指摘したように)1つの追加のjmpを使用すると、パフォーマンスに大きな違いが生じることはありません。
セマンティックディファレンスはありません。コンパイルされたディファレンスは必要ありません。しかし、それはコンパイラに依存します。そこで、g ++ 4.3.2、CC 5.5、およびxlc6で試してみました。
g ++、CCは同一でした、xlcはそうではありませんでした
xlcの違いは、最初のループエントリにありました。
extern int doit( int );
void loop1( ) {
for ( int ii = 0; ii < 10; ii++ ) {
doit( ii );
}
}
void loop2() {
int ii = 0;
while ( ii < 10 ) {
doit( ii );
ii++;
}
}
XLC出力
.loop2: # 0x00000000 (H.10.NO_SYMBOL)
mfspr r0,LR
stu SP,-80(SP)
st r0,88(SP)
cal r3,0(r0)
st r3,64(SP)
l r3,64(SP) ### DIFFERENCE ###
cmpi 0,r3,10
bc BO_IF_NOT,CR0_LT,__L40
...
enter code here
.loop1: # 0x0000006c (H.10.NO_SYMBOL+0x6c)
mfspr r0,LR
stu SP,-80(SP)
st r0,88(SP)
cal r3,0(r0)
cmpi 0,r3,10 ### DIFFERENCE ###
st r3,64(SP)
bc BO_IF_NOT,CR0_LT,__La8
...
ループのテストでの変数のスコープはwhile
、ループのヘッダーで宣言された変数のスコープよりも広くなっていfor
ます。
したがって、変数をより長く存続させることの副作用としてパフォーマンスに影響がある場合は、whileループとforループのどちらかを選択することでパフォーマンスに影響があります(whileを{}でラップしてその範囲を縮小しないでください)。変数)。
例としては、それを参照するイテレーターの数をカウントする並行コレクションがあります。複数のイテレーターが存在する場合は、同時変更を防ぐためにロックを適用しますが、最適化として、1つのイテレーターのみがそれを参照する場合はロックを除外します。次にfor
、同じコンテナで異なる名前のイテレータを使用する関数に2つのループがある場合、高速パスが使用されますが、2つのwhile
ループを使用すると、低速パスが使用されます。同様に、オブジェクトが大きい(キャッシュトラフィックが多い)場合、またはシステムリソースを使用する場合は、パフォーマンスに影響が出る可能性があります。しかし、それがどこで違いを生むかを見たことがない実際の例を考えることはできません。
ループ展開を使用して最適化するコンパイラーは、おそらくforループの場合にのみ最適化します。
どちらも同等です。それは意味論の問題です。
唯一の違いは、do ... while構文にある可能性があります。この場合、状態の評価を体の後まで延期するため、1つの評価を節約できます。
i = 1; do { ... i--; } while( i > 0 );
とは対照的に
for( i = 1; i > 0; --i )
{ ....
}
私はコンパイラを書きます。すべての「構造化された」制御フロー(、、、、、... )を条件付きおよび無条件の分岐にif
コンパイルします。次に、制御フローグラフを分析します。とにかくCコンパイラは一般的な処理を行う必要があるため、すべてを分岐命令と条件分岐命令に減らすのが最も簡単です。次に、その場合を適切に処理するようにしてください。(ACコンパイラーは、手書きのコードだけでなく、自動生成されたコードに対しても適切に機能する必要があります。これには、多くのステートメントが含まれる場合があります。)while
for
switch
do
while
goto
goto
いいえ。同等のことをしている場合は、同じコードにコンパイルされます。あなたが言うように、それはセマンティクスに関するものです。表現しようとしていることを最もよく表すものを選択してください。
理想的には同じである必要がありますが、最終的にはコンパイラ/インタプリタによって異なります。確かに、生成されたアセンブリコードを測定または調べる必要があります。
違いがある可能性があることの証明:これらの行は、cc65を使用して異なるアセンブリコードを生成します。
for (; i < 1000; ++i);
while (i < 1000) ++i;
Atmel ATMegaでは、while()はfor()よりも高速です。これがAVR035:AVRの効率的なCコーディングで説明されているのはなぜですか。
PSオリジナルプラットフォームは問題に言及されていませんでした。
継続は、 forとwhileで異なる動作をします:in for、それはカウンターを変更します、in while、それは通常は変更しません
別の答えを追加すると、私の経験では、ソフトウェアの最適化は、大きくてふさふさしたあごひげが男性から剃られているようなものです。
- まず、はさみで大きな塊に切り落とします(手足全体をコールツリーから剪定します)。
- 次に、電気バリカンで短くします(アルゴリズムを微調整します)。
- 最後に、かみそりでそれを剃って、最後の少しを取り除きます(低レベルの最適化)。
最後は、との違いが違いfor()
をwhile()
生むかもしれないが、おそらくそうではない場所です。
PS私が知っているプログラマー(全員が非常に優秀で、代表的なサンプルだと思います)は、基本的に反対方向からそれを実行します。
パフォーマンスに関しては同じです。while
私は、状態の変化を待つとき(バッファーがいっぱいになるのを待つなど)やfor
、多数の個別のオブジェクトを処理するとき(コレクション内の各アイテムを通過するなど)に使用する傾向があります。
場合によっては違いがあります。
その違いが重要な段階にある場合は、より適切なアルゴリズムを選択するか、アセンブリ言語でコーディングを開始する必要があります。信頼してください。コンパイラのバージョンを修正するよりも、アセンブリでコーディングする方が望ましいです。
while()
より速い/遅いですfor()
か?最適化に関するいくつかのことを確認しましょう。
コンパイラー作成者は、ジャンプ、比較、インクリメント、および生成するその他の種類の命令の呼び出しを少なくすることで、サイクルを短縮するために非常に懸命に取り組んでいます。
一方、呼び出し命令は、はるかに多くのサイクルを消費しますが、コンパイラーはそれらを削除するために何もすることはほとんど無力です。
プログラマーとして、私たちはたくさんの関数呼び出しを作成します。それは、私たちが意図しているため、怠惰なため、そしてコンパイラーが明白にせずにそれらを挿入するためです。
ほとんどの場合、それは問題ではありません。ハードウェアが非常に高速で、私たちの仕事が非常に小さいため、コンピューターはビーグル犬のように食べ物を狼狽し、もっと物乞いをします。
ただし、場合によっては、パフォーマンスが問題になるほどジョブが大きいことがあります。
それではどうしますか?より大きな見返りはどこにありますか?
- コンパイラにループなどを数サイクルオフにするようにしていますか?
- それほど多くのことを行う必要がない関数呼び出しを見つけることはできますか?
コンパイラは後者を実行できません。私たちプログラマーだけができます。
私たちはこれを行う方法を学ぶか、教えられる必要があります。自然には来ません。私たちは先天的に間違った推測をして、それに賭ける傾向があります。より良いアルゴリズムを取得することは始まりですが、始まりにすぎません。確かに彼らが方法を知っているならば、私たちの教師はこれを教える必要があります。
プロファイラーは出発点です。私はこれをします。
ウィリー・サットンの外典の引用は、なぜ銀行を奪うのですか?:
そこにお金があるからです。
サイクルを節約したい場合は、それらがどこにあるかを調べてください。
おそらくコーディングスタイルのみ。
- 反復回数がわかっている場合。
- 一方、反復回数がわからない場合。