2017-02-09 3 views
10

私は宿題をして、forループの内側または外側に変数を宣言するかどうかにかかわらず、パフォーマンスに違いがないことを繰り返し確信しています。実際には同じMSILにコンパイルされます。それにもかかわらず、私はそれにもかかわらず、ループ内で変数宣言を移動すると、実際にはかなりの一貫性のあるパフォーマンスが得られることが分かりました。forループ内で宣言された変数は、ループのパフォーマンスに影響しますか?

私はこの効果を測定するための小さなコンソールテストクラスを作成しました。私は静的double[]配列の項目をに初期化し、2つのメソッドがループ操作を実行し、結果を静的double[]配列バッファに書き出します。元々、私の方法は、私が違いに気づいたもの、すなわち複素数の大きさの計算でした。 の項目をの長さ1000000の配列で100回実行すると、変数(の変数)がループ内にあるものの実行時間が一貫して低くなります。たとえば、32,83±0.64 ms v 43 、Intel Core 2 Duo @ 2.66 GHzの高齢者構成では24±0.45 msです。私はそれらを異なる順序で実行しようとしましたが、結果には影響しませんでした。

は、その後、私は複素数の大きさを計算することは最低限の作業例から遠く離れていることを実現し、2つのはるかに簡単な方法をテストした:変数を宣言:これらで

static void Square1() 
    { 
     double x; 

     for (int i = 0; i < buffer.Length; i++) { 
      x = items[i]; 
      buffer[i] = x * x; 
     } 
    } 


    static void Square2() 
    { 
     for (int i = 0; i < buffer.Length; i++) { 
      double x; 
      x = items[i]; 
      buffer[i] = x * x; 
     } 
    } 

を、結果は他の方法を出てきましたループの外側がより好ましいように見えた:Square1()の7.07±0.43ミリ秒Square2()の12.07±0.51ミリ秒。

私はILDASMに精通していないですが、私は2つのメソッドを解体している、との唯一の違いは、ローカル変数の初期化のようだ:Square1() V

 .locals init ([0] int32 i, 
     [1] float64 x, 
     [2] bool CS$4$0000) 

 .locals init ([0] float64 x, 
     [1] int32 i, 
     [2] bool CS$4$0000) 

Square2()にあります。それに応じて、stloc.1が1つで、それはstloc.0であり、その逆である。より複雑な大きさの計算MSILコードでは、コードサイズが異なっていて、内部宣言コードにstloc.0があった外部宣言コードでstloc.s iが見えました。

これはどのようにすることができますか?私は何かを見落としているのですか、それとも本当の効果ですか?そうであれば、長いループのパフォーマンスに大きな違いをもたらす可能性があるので、議論に値すると思う。

あなたのご意見は大変ありがとうございます。

編集:私が見落としたことの1つは、投稿する前に複数のコンピュータでテストすることでした。私は今i5でそれを実行しており、の結果は2つの方法でほぼ同じです。このような誤解を招くような観測結果を投稿したことをお詫び申し上げます。

+1

良い調査、あなたは必ずupvoteを獲得してください。 – NicoRiff

+2

@NicoRiff:確かに、よく書かれた質問です。 (悲しいことに、私は答えは自明だと思うが) – Bathsheba

+1

これで@JonSkeet答えを待つことができない – NicoRiff

答えて

5

その塩の価値があるC#コンパイラは、このようなマイクロ最適化を実行します。スコープ外の変数が必要な場合にのみリークします。

可能であれば、ループの内部にはdouble x;を入れてください。

個人的には、items[i]が平凡なデータ配列のアクセスであれば、私はbuffer[i] = items[i] * items[i];と書いています。 CとC++はそれを最適化しますが、私はC#が(まだ)行っていないと思います。あなたの解体はそうではないことを意味します。

+1

しかし、それはなぜですか? – NicoRiff

+0

ありがとうございました!私は方法の冒頭で私の変数を宣言するという強迫的な習慣に固執していましたが、今から2回考えます。 私のtake-homeメッセージは、最適化が両方向で機能するように見えるので、パフォーマンスを気にするなら両方のアレンジをテストすることです。 –

+1

ふわふわした答えは、「長年の経験は、コードベースの完全な混乱の中でゆるやかなスコープの変数が終わることを示している」ということです。 – Bathsheba

1

ガベージコレクタがこれらの2つのバリアントに対して何を行うかをプロファイルすることは興味深いでしょう。

最初のケースでは、ループが実行されている間に変数xは収集されませんでした。なぜなら、ループは外部スコープで宣言されているからです。

xのすべてのハンドルは、各繰り返しで削除されます。

新しいC#4.6 GC.TryStartNoGCRegionGC.EndNoGCRegionでテストをもう一度実行して、パフォーマンスの影響がGCに起因するかどうかを確認することもできます。

Prevent .NET Garbage collection for short period of time

+0

ありがとう、それは素晴らしい考えです。私はすでにテストしたかったのですが、現時点では.NET 4.6へのアクセス権がありません。 SharpDevelopはそれをサポートしていないようです。ツールをアップグレードして質問に戻るようにします。 –

+1

これはGCとは関係がないと私は疑います。 'double'は値型であり、この場合はスタック割り当てになります。クリーンアップするためのゴミを生成しません。 – Kyle

+2

Eric Lippertはこれに関する優れた情報をhttp://stackoverflow.com/a/14043763/526724で提供しています –

関連する問題