2012-07-13 10 views
7

ここでは起動時間が異なることに気付きました。初期化コードを配置しました。私はこれが本当に奇妙だと思ったので、小さなベンチマークを書いて、私の疑惑を確認しました。メインメソッドが呼び出される前に実行されるコードは、通常よりも遅いようです。静的コンストラクタのコードが遅く実行される

通常のコードパスの前後で呼び出されるかによって、Benchmark();が異なる速度で動作するのはなぜですか?

ここではベンチマークのコードです:

class Program { 
    static Stopwatch stopwatch = new Stopwatch(); 
    static Program program = new Program(); 

    static void Main() { 
     Console.WriteLine("main method:"); 
     Benchmark(); 
     Console.WriteLine(); 

     new Program(); 
    } 

    static Program() { 
     Console.WriteLine("static constructor:"); 
     Benchmark(); 
     Console.WriteLine(); 
    } 

    public Program() { 
     Console.WriteLine("public constructor:"); 
     Benchmark(); 
     Console.WriteLine(); 
    } 

    static void Benchmark() { 
     for (int t = 0; t < 5; t++) { 
      stopwatch.Reset(); 
      stopwatch.Start(); 
      for (int i = 0; i < 1000000; i++) 
       IsPrime(2 * i + 1); 
      stopwatch.Stop(); 
      Console.WriteLine(stopwatch.ElapsedMilliseconds + " ms"); 
     } 
    } 

    static Boolean IsPrime(int x) { 
     if ((x & 1) == 0) 
      return x == 2; 
     if (x < 2) 
      return false; 
     for (int i = 3, s = (int)Math.Sqrt(x); i <= s; i += 2) 
      if (x % i == 0) 
       return false; 
     return true; 
    } 
} 

結果はBenchmark()static Program programプロパティの静的コンストラクタと、コンストラクタの両方のためにほぼ倍の実行速度が遅いことを示しています

における反復回数を倍増
// static Program program = new Program() 
public constructor: 
894 ms 
895 ms 
887 ms 
884 ms 
883 ms 

static constructor: 
880 ms 
872 ms 
876 ms 
876 ms 
872 ms 

main method: 
426 ms 
428 ms 
426 ms 
426 ms 
426 ms 

// new Program() in Main() 
public constructor: 
426 ms 
427 ms 
426 ms 
426 ms 
426 ms 

は、ベンチマーク・ループはすべての時間を倍にし、発生するパフォーマンス・ペナルティが一定ではなく、要因であることを示唆しています。

// static Program program = new Program() 
public constructor: 
2039 ms 
2024 ms 
2020 ms 
2019 ms 
2013 ms 

static constructor: 
2019 ms 
2028 ms 
2019 ms 
2021 ms 
2020 ms 

main method: 
1120 ms 
1120 ms 
1119 ms 
1120 ms 
1120 ms 

// new Program() in Main() 
public constructor: 
1120 ms 
1128 ms 
1124 ms 
1120 ms 
1122 ms 

これはどうしてですか?初期化がそれが所属する場所で完了した場合と同じ速さであれば意味があります。テストは.NET 4で、リリースモードで、最適化を行った。

+2

正確な質問は何ですか? – jcolebrand

+0

設定をコンパイルしますか?フレームワークのバージョン? – user7116

+0

私の編集が意味を持つかどうか確認してください(私には分かりません).Net 4/releaseで同様の結果を試しました。 –

答えて

3

これは非常に興味深い問題です。あなたのプログラムの変種を試してみるのに時間を費やしました。ここではいくつかの観測されています

  1. あなたが別のクラスにベンチマーク()静的メソッドを移動した場合、静的コンストラクタのパフォーマンスペナルティが消えます。

  2. ベンチマーク()メソッドをインスタンスメソッドにすると、パフォーマンスのペナルティが消えます。

  3. 私があなたの高速ケース(1,2)と低速ケース(3,4)をプロファイリングすると、CLRヘルパメソッド、特にJIT_GetSharedNonGCStaticBase_Helperで余分な時間が費やされました。

この情報に基づいて、私は何が起こっているのか推測することができます。 CLRでは、すべての静的コンストラクタが最大で1回実行されるようにする必要があります。複雑なことは、静的コンストラクタがサイクルを形成し得ることである(例えば、クラスAがタイプBの静的フィールドを含み、クラスBがタイプAの静的フィールドを含む場合)。

静的コンストラクタの内部で実行する場合、JITコンパイラはいくつかの静的メソッド呼び出しをチェックして、周期的なクラス依存関係のために潜在的な無限サイクルを防止します。 staticメソッドが静的コンストラクタの外部から呼び出されると、CLRはメソッドを再コンパイルしてチェックを削除します。

これは起こっていることにかなり近いはずです。

+0

問題、特にプロファイリングを調べてくれてありがとう。私はあなたの分析については確信していません。私のテストでは一定のオーバーヘッドを意味します。私はパフォーマンスのペナルティが要因であるように見えるように私の質問を更新しました。 – Zong

+0

各IsPrimeコールはチェックで囲まなければならないので、各IsPrime()コール**には余分のオーバーヘッド**があります。たとえば、IsPrimeをより高価にする(たとえば、IsPrime(1000000000 + 2 * i)を呼び出す)場合、4つのケースの違いは消えます。 –

+0

つまり、反復回数を2倍にすると、IsPrime呼び出しの数も2倍になり、実行された検査の数が倍になります。 –

3

これは非常によく文書化された事実です。

静的コンストラクタは低速です。 .NETランタイムは、それらを最適化するほどスマートではありません。

は参照してください:彼らはランタイムが、クラスの任意のメンバーがアクセスされる前に、値が正確に 設定されていることを保証することを要求 のでPerformance penalty of static constructors

明示的な静的コンストラクタは高価です。正確なコストは ですが、シナリオによって異なりますが、場合によってはかなり目立つこともあります。

+0

私はリンクされた記事が当てはまるとは思わない。この記事では、staticコンストラクタを持つ型は、beforeFieldInitとマークされているため、その型で作業すると余分なランタイムコストが発生します。しかし、OPは、2つのタイプを比較していません.1つは静的コンストラクターと、もう1つはタイプなしです。ベンチマーク全体には1つの型しかなく、その型には静的なコンストラクタがあります。だから、何か他のことが起こっている。 –

+0

あなたは正しいです。私のベンチマークでは、記事の2つのケースのパフォーマンスが多少同等であることが実際に示唆されています。 – Zong

関連する問題