15

更新:私が予想したように、この質問に対するコミュニティの適切なアドバイスは、「それを測定して見る」ことでした。chibacityは、私のためにこれを行ったいくつかの本当に素晴らしいテストで答えを投稿しました。その間、私は自分のテストを書きました。そして、私が見たパフォーマンスの違いは実際には非常に大きかったので、それについてのブログ投稿を書くことを余儀なくされたと感じました。

ただし、属性は実際には無料ではなく、実際にはその魔法を機能させるためにCLRヘルパーメソッドに依存しているというハンスの説明も認める必要があります。ThreadStaticこれは、任意の場合に適用することが適切な最適化であるかどうかを明らかにすることにはほど遠いです。

私にとって良いニュースは、私の場合、それが大きな改善をしたように見えるということです。


私は(他の多くのものの中でも)いくつかの中型配列(〜50要素)をいくつかのローカル変数に対してインスタンス化するメソッドを持っています。

いくつかのプロファイリングの後、私はこの方法をパフォーマンスのボトルネックのようなものとして特定しました。メソッドの呼び出しに非常に長い時間がかかるわけではありません。むしろ、それは単に度も、非常に迅速に呼び出されます(セッションで数十万から数百万回、これは数時間になります)。したがって、パフォーマンスを比較的わずかに改善するだけでも価値があります。

呼び出しごとに新しい配列を割り当てる代わりに、[ThreadStatic];とマークされたフィールドを使用できることに気づきました。メソッドが呼び出されるたびに、フィールドが現在のスレッドで初期化されているかどうかがチェックされ、初期化されていない場合は初期化されます。その時点から、同じスレッドでのすべての呼び出しで、その時点ですべての配列を実行できるようになります。

(このメソッドは配列自体のすべての要素を初期化するため、配列に「古い」要素があることは問題になりません。)

私の質問は単純にこれです:これは良い考えのように見えますか?ThreadStaticこのように属性を使用すること(つまり、ローカル変数の新しいオブジェクトをインスタンス化するコストを軽減するためのパフォーマンスの最適化として)に知っておくべき落とし穴はありますか?ThreadStaticフィールド自体のパフォーマンスはおそらく素晴らしいものではありませんか。たとえば、この機能を可能にするために、バックグラウンドで多くの余分な「もの」が発生し、独自のコストが発生しますか?

また、50要素の配列のように安価なもの(?)を最適化しようとするのは間違っていることも私には非常に妥当です。そうであれば、間違いなく知らせてください。しかし、一般的な質問は依然として当てはまります。

4

3 に答える 3

9

[ThreadStatic]は無料のランチではありません。変数へのすべてのアクセスは、ジッターによってインラインでコンパイルされるのではなく、CLRのヘルパー関数(JIT_GetThreadFieldAddr_Primitive / Objref)を経由する必要があります。また、ローカル変数の真の代替ではなく、再帰はバイトになります。あなたは本当にこれを自分でプロファイリングする必要があります、ループ内のその多くのCLRコードでパフォーマンスを推測することは実行可能ではありません。

于 2011-02-01T17:22:09.133 に答える
5

私は単純なベンチマークをThreadStatic実行し、質問で説明されている単純なパラメーターのパフォーマンスが向上しています。

反復回数が多い多くのアルゴリズムと同様に、新しい配列を割り当てるバージョンでは、GCオーバーヘッドがそれを強制終了する単純なケースだと思います。

アップデート

最小限の配列参照の使用をモデル化するための配列の追加の反復を含むテスト、およびThreadStatic参照がローカルにコピーされた以前のテストに加えて、配列参照の使用:

Iterations : 10,000,000

Local ArrayRef          (- array iteration) : 330.17ms
Local ArrayRef          (- array iteration) : 327.03ms
Local ArrayRef          (- array iteration) : 1382.86ms
Local ArrayRef          (- array iteration) : 1425.45ms
Local ArrayRef          (- array iteration) : 1434.22ms
TS    CopyArrayRefLocal (- array iteration) : 107.64ms
TS    CopyArrayRefLocal (- array iteration) : 92.17ms
TS    CopyArrayRefLocal (- array iteration) : 92.42ms
TS    CopyArrayRefLocal (- array iteration) : 92.07ms
TS    CopyArrayRefLocal (- array iteration) : 92.10ms
Local ArrayRef          (+ array iteration) : 1740.51ms
Local ArrayRef          (+ array iteration) : 1647.26ms
Local ArrayRef          (+ array iteration) : 1639.80ms
Local ArrayRef          (+ array iteration) : 1639.10ms
Local ArrayRef          (+ array iteration) : 1646.56ms
TS    CopyArrayRefLocal (+ array iteration) : 368.03ms
TS    CopyArrayRefLocal (+ array iteration) : 367.19ms
TS    CopyArrayRefLocal (+ array iteration) : 367.22ms
TS    CopyArrayRefLocal (+ array iteration) : 368.20ms
TS    CopyArrayRefLocal (+ array iteration) : 367.37ms
TS    TSArrayRef        (+ array iteration) : 360.45ms
TS    TSArrayRef        (+ array iteration) : 359.97ms
TS    TSArrayRef        (+ array iteration) : 360.48ms
TS    TSArrayRef        (+ array iteration) : 360.03ms
TS    TSArrayRef        (+ array iteration) : 359.99ms

コード:

[ThreadStatic]
private static int[] _array;

[Test]
public object measure_thread_static_performance()
{
    const int TestIterations = 5;
    const int Iterations = (10 * 1000 * 1000);
    const int ArraySize = 50;

    Action<string, Action> time = (name, test) =>
    {
        for (int i = 0; i < TestIterations; i++)
        {
            TimeSpan elapsed = TimeTest(test, Iterations);
            Console.WriteLine("{0} : {1:F2}ms", name, elapsed.TotalMilliseconds);
        }
    };

    int[] array = null;
    int j = 0;

    Action test1 = () =>
    {
        array = new int[ArraySize];
    };

    Action test2 = () =>
    {
        array = _array ?? (_array = new int[ArraySize]);
    };

    Action test3 = () =>
    {
        array = new int[ArraySize];

        for (int i = 0; i < ArraySize; i++)
        {
            j = array[i];
        }
    };

    Action test4 = () =>
    {
        array = _array ?? (_array = new int[ArraySize]);

        for (int i = 0; i < ArraySize; i++)
        {
            j = array[i];
        }
    };

    Action test5 = () =>
    {
        array = _array ?? (_array = new int[ArraySize]);

        for (int i = 0; i < ArraySize; i++)
        {
            j = _array[i];
        }
    };

    Console.WriteLine("Iterations : {0:0,0}\r\n", Iterations);
    time("Local ArrayRef          (- array iteration)", test1);
    time("TS    CopyArrayRefLocal (- array iteration)", test2);
    time("Local ArrayRef          (+ array iteration)", test3);
    time("TS    CopyArrayRefLocal (+ array iteration)", test4);
    time("TS    TSArrayRef        (+ array iteration)", test5);

    Console.WriteLine(j);

    return array;
}

[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.GC.Collect")]
private static TimeSpan TimeTest(Action action, int iterations)
{
    Action gc = () =>
    {
        GC.Collect();
        GC.WaitForFullGCComplete();
    };

    Action empty = () => { };

    Stopwatch stopwatch1 = Stopwatch.StartNew();

    for (int j = 0; j < iterations; j++)
    {
        empty();
    }

    TimeSpan loopElapsed = stopwatch1.Elapsed;

    gc();
    action(); //JIT
    action(); //Optimize

    Stopwatch stopwatch2 = Stopwatch.StartNew();

    for (int j = 0; j < iterations; j++) action();

    gc();

    TimeSpan testElapsed = stopwatch2.Elapsed;

    return (testElapsed - loopElapsed);
}
于 2011-02-01T17:33:14.190 に答える
2

このような結果から、ThreadStaticはかなり高速に見えます。ただし、50要素の配列を再割り当てするよりも速いかどうかについて、誰かが具体的な答えを持っているかどうかはわかりません。それはあなたがあなた自身をベンチマークしなければならないようなものです。:)

それが「良い考え」であるかどうかにかかわらず、私はやや引き裂かれています。すべての実装の詳細がクラス内に保持されている限り、それは必ずしも悪い考えではありません(呼び出し元がそれについて心配する必要はありません)が、ベンチマークがこのメソッドからのパフォーマンスの向上を示さない限り、私は単純に固執しますコードがより単純で読みやすくなるため、毎回配列を割り当てます。2つのソリューションのうち、より複雑なものとして、これを選択する前に、複雑さのメリットを確認する必要があります。

于 2011-02-01T17:00:34.457 に答える