次のようなことをした場合、速度や効率が低下するかどうか疑問に思っています。
int i = 0;
while(i < 100)
{
int var = 4;
i++;
}
int var
100回を宣言します。あるように思えますが、よくわかりません。代わりにこれを行う方がより実用的/高速でしょうか:
int i = 0;
int var;
while(i < 100)
{
var = 4;
i++;
}
それとも、速度と効率の点で同じですか?
次のようなことをした場合、速度や効率が低下するかどうか疑問に思っています。
int i = 0;
while(i < 100)
{
int var = 4;
i++;
}
int var
100回を宣言します。あるように思えますが、よくわかりません。代わりにこれを行う方がより実用的/高速でしょうか:
int i = 0;
int var;
while(i < 100)
{
var = 4;
i++;
}
それとも、速度と効率の点で同じですか?
通常、ローカル変数のスタック領域は関数スコープに割り当てられます。したがって、ループ内ではスタック ポインターの調整は行われず、4 が に代入されるだけvar
です。したがって、これら 2 つのスニペットのオーバーヘッドは同じです。
プリミティブ型と POD 型の場合、違いはありません。どちらの場合も、コンパイラは関数の先頭で変数にスタック領域を割り当て、関数が戻るときに割り当てを解除します。
自明でないコンストラクターを持つ非 POD クラス型の場合、違いが生じます。その場合、変数をループの外に置くと、コンストラクターとデストラクタは 1 回だけ呼び出され、代入演算子は反復ごとに呼び出されます。 loop は、ループの反復ごとにコンストラクタとデストラクタを呼び出します。クラスのコンストラクタ、デストラクタ、および代入演算子が何をするかによって、これが望ましい場合とそうでない場合があります。
どちらも同じであり、コンパイラが何をするかを見ることで、次のように見つけることができます (最適化が高く設定されていなくても):
コンパイラ (gcc 4.0) が単純な例に対して行うことを見てください。
1.c:
main(){ int var; while(int i < 100) { var = 4; } }
gcc -S 1.c
1.s:
_main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl $0, -16(%ebp)
jmp L2
L3:
movl $4, -12(%ebp)
L2:
cmpl $99, -16(%ebp)
jle L3
leave
ret
2.c
main() { while(int i < 100) { int var = 4; } }
gcc -S 2.c
2.s:
_main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl $0, -16(%ebp)
jmp L2
L3:
movl $4, -12(%ebp)
L2:
cmpl $99, -16(%ebp)
jle L3
leave
ret
これらから、2 つのことがわかります。まず、コードは両方で同じです。
次に、var のストレージがループの外に割り当てられます。
subl $24, %esp
最後に、ループ内の唯一のことは、割り当てと条件チェックです。
L3:
movl $4, -12(%ebp)
L2:
cmpl $99, -16(%ebp)
jle L3
これは、ループを完全に削除しなくてもできる限り効率的です。
最近では、コンパイラがコードをより適切に最適化できるようになるため (変数のスコープを縮小する)、定数でない限り、ループ内で宣言することをお勧めします。
編集:この回答は現在ほとんど廃止されています。ポストクラシック コンパイラの台頭により、コンパイラがそれを理解できないケースはまれになりつつあります。私はまだそれらを構築できますが、ほとんどの人はその構築を悪いコードとして分類します。
最新のコンパイラのほとんどは、これを最適化します。そうは言っても、最初の例の方が読みやすいので、最初の例を使用します。
組み込み型の場合、2つのスタイルの間に違いはない可能性があります(おそらく、生成されたコードに至るまで)。
ただし、変数が重要なコンストラクタ/デストラクタを持つクラスである場合、実行時のコストに大きな違いが生じる可能性があります。私は通常、変数をループの内側にスコープします(スコープをできるだけ小さく保つため)が、それがパフォーマンスに影響を与えることが判明した場合は、クラス変数をループのスコープの外側に移動することを検討します。ただし、これを行うには、odeパスのセマンティクスが変更される可能性があるため、追加の分析が必要です。したがって、これは、セマンティクスで許可されている場合にのみ実行できます。
RAIIクラスはこの動作を必要とする場合があります。たとえば、ファイルアクセスの有効期間を管理するクラスは、ファイルアクセスを適切に管理するために、ループの反復ごとに作成および破棄する必要がある場合があります。
LockMgr
構築時にクリティカルセクションを取得し、破棄すると解放するクラスがあるとします。
while (i< 100) {
LockMgr lock( myCriticalSection); // acquires a critical section at start of
// each loop iteration
// do stuff...
} // critical section is released at end of each loop iteration
とはかなり異なります:
LockMgr lock( myCriticalSection);
while (i< 100) {
// do stuff...
}
両方のループの効率は同じです。どちらも無限の時間がかかります:) ループ内で i をインクリメントすることをお勧めします。
#include <stdio.h>
int main()
{
for(int i = 0; i < 10; i++)
{
int test;
if(i == 0)
test = 100;
printf("%d\n", test);
}
}
上記のコードは常に 100 を 10 回出力します。これは、ループ内のローカル変数が関数呼び出しごとに 1 回だけ割り当てられることを意味します。
確実にする唯一の方法は、時間を計ることです。しかし、違いがあるとしても、その差は微視的であるため、非常に大きなタイミング ループが必要になります。
要するに、最初のものは変数 var を初期化するため、スタイルが優れていますが、もう 1 つは初期化されていないままです。これと、変数をできるだけ使用点に近い場所で定義する必要があるというガイドラインは、通常、最初の形式が優先されることを意味します。
変数が 2 つしかない場合、コンパイラは両方にレジスタを割り当てる可能性があります。これらのレジスタはとにかくそこにあるので、これには時間がかかりません。どちらの場合も、2 つのレジスタ書き込み命令と 1 つのレジスタ読み出し命令があります。
それは真実ではありませんが、オーバーヘッドがありますが、無視できるオーバーヘッドがあります。
おそらくそれらはスタックの同じ場所に配置されますが、まだ割り当てられています。そのintのスタック上のメモリ位置を割り当て、}の終わりに解放します。ヒープフリーの意味ではなく、sp(スタックポインター)を1移動します。そして、あなたの場合、ローカル変数が1つしかないことを考えると、単にfp(フレームポインター)とspを同等にします
簡単な答えは次のとおりです。どちらの方法でもほとんど同じように機能します。
しかし、スタックがどのように編成されているかについてもっと読んでみてください。私の学部では、それについてかなり良い講義がありまし た