7

初期化コードを配置した場所によって、起動時間が異なることに気付きました。これは本当に奇妙だと思ったので、私の疑いを裏付ける小さなベンチマークを作成しました。main メソッドが呼び出される前に実行されるコードは、通常よりも遅いようです。

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プロパティのコンストラクターの両方で、ほぼ 2 倍遅く実行されることを示しています。

// 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

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

// 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、リリース モード、最適化で行われました。

4

2 に答える 2

3

これは非常に興味深い問題です。私はあなたのプログラムの変種を実験するのに少し時間を費やしました。ここにいくつかの観察があります:

  1. Benchmark()静的メソッドを別のクラスに移動すると、静的コンストラクターのパフォーマンスの低下がなくなります。

  2. Benchmark()メソッドをインスタンスメソッドにすると、パフォーマンスの低下はなくなります。

  3. 速いケース(1、2)と遅いケース(3、4)のプロファイルを作成すると、遅いケースはCLRヘルパーメソッド、特にJIT_GetSharedNonGCStaticBase_Helperに余分な時間を費やしました。

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

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

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

于 2012-07-15T01:11:22.443 に答える
3

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

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

参照:静的コンストラクターのパフォーマンス ペナルティ

明示的な静的コンストラクターは、クラスのメンバーがアクセスされる直前に値が設定されることをランタイムが保証する必要があるため、コストがかかります。正確なコストはシナリオによって異なりますが、場合によっては非常に目立つ可能性があります。

于 2012-07-13T04:08:29.657 に答える