web-dev-qa-db-ja.com

ループ内で変数を宣言するオーバーヘッドはありますか? (C ++)

あなたがこのようなことをした場合、速度または効率の損失があるかどうか疑問に思っています:

int i = 0;
while(i < 100)
{
    int var = 4;
    i++;
}

int var 100回。あるように思えますが、よくわかりません。代わりにこれを行う方がより実用的/高速ですか?

int i = 0;
int var;
while(i < 100)
{
    var = 4;
    i++;
}

または、それらは同じで、速度と効率が同じですか?

149
user98188

ローカル変数のスタックスペースは、通常、関数スコープで割り当てられます。そのため、ループ内でスタックポインターの調整は行われず、varに4が割り当てられます。したがって、これらの2つのスニペットには同じオーバーヘッドがあります。

189
laalto

プリミティブ型とPOD型の場合、違いはありません。コンパイラーは、関数の先頭で変数にスタックスペースを割り当て、両方の場合で関数が戻るときにその変数の割り当てを解除します。

自明ではないコンストラクタを持つ非PODクラスタイプの場合、違いが生じます。その場合、変数をループ外に配置すると、コンストラクタとデストラクタが1回だけ呼び出され、各反復で代入演算子が呼び出されます。 loopは、ループのすべての反復に対してコンストラクターとデストラクターを呼び出します。クラスのコンストラクタ、デストラクタ、および代入演算子が何をするかに応じて、これは望ましい場合と望ましくない場合があります。

98
Adam Rosenfield

これらは両方とも同じであり、コンパイラーが何を行うかを調べることで、最適化を高に設定しなくても、以下のようにして調べることができます。

コンパイラ(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

これは、ループを完全に削除することなく、できる限り効率的です。

66
Alex Brown

最近では、コンパイラーがコードをより適切に最適化できる(変数の範囲を縮小する)ことができるため、定数でない限り、ループ内で宣言する方が適切です。

編集:この答えは現在ほとんど廃止されています。ポストクラシックコンパイラの台頭により、コンパイラがそれを理解できない場合はまれになっています。私はまだそれらを構築できますが、ほとんどの人はその構築を悪いコードとして分類します。

12
Joshua

最新のコンパイラは、これを最適化します。そうは言っても、読みやすいと思うので最初の例を使用します。

10
Andrew Hare

組み込み型の場合、おそらく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...

}
8
Michael Burr

両方のループの効率は同じです。両方とも無限の時間がかかります:)ループ内でiをインクリメントするのは良い考えかもしれません。

6
Larry Watanabe

私はかつていくつかのパフォーマンステストを実行しましたが、驚いたことに、ケース1の方が実際に速いことがわかりました。これは、ループ内で変数を宣言するとスコープが狭くなるため、より早く解放されるためだと思われます。ただし、非常に古いコンパイラでは、それはかなり前のことです。最新のコンパイラーが差異を最適化するより良い仕事をしていると確信していますが、それでも変数のスコープをできるだけ短く保つことは害になりません。

2
user3864776
#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回しか割り当てられません。

2
Byeonggon Lee

確認する唯一の方法は、時間を計ることです。ただし、違いがある場合は微視的であるため、強力な大きなタイミングループが必要になります。

要するに、最初の変数は変数varを初期化するため、より良いスタイルであり、もう1つの変数は初期化されないままです。これと、使用ポイントにできるだけ近い場所で変数を定義する必要があるガイドラインは、通常、最初の形式が優先されることを意味します。

0
anon