21

並列 foreach によって実行される作業を表す次の同時実行パフォーマンス分析を参照してください。

ここに画像の説明を入力

ループ内では、各スレッドが DB からデータを読み取り、処理します。それぞれが異なるデータを処理するため、スレッド間にロックはありません。

理由は不明ですが、foreach のすべてのスレッドで定期的なロックが発生しているようです (黒い垂直の長方形を参照)。選択されたロックされたセグメント (濃い赤のセグメント) が表示される場合、スタックが StockModel.Quotation コンストラクターでロックされたスレッドを示していることがわかります。そこのコードは、2 つの空のリストを構築するだけです!

これはGCが原因である可能性があることをどこかで読んだので、ガベージコレクションをサーバーモードで実行するように変更しました:

<runtime>
    <gcServer enabled="true"/>
</runtime>

少し改善されましたが (約 10% - 15% 速くなりました)、まだどこにでも垂直ロックがあります。

また、違いなしでデータを読み取るだけなので、すべての DB クエリに WITH(NOLOCK) を追加しました。

ここで何が起こっているかについてのヒントはありますか?

分析が行われたコンピューターには 8 つのコアがあります。

編集: Microsoft Symbol サーバーを有効にすると、wait_gor_gc_done や WaitUntilGCComplete などの呼び出しですべてのスレッドがブロックされることが判明しました。GCServer を有効にすると、スレッドごとに 1 つの GC があるため、「垂直」ロックを回避できると思いましたが、そうではないようです。私が間違っている?

2 番目の質問: マシンがメモリに負荷をかけられていない (8 ギガのうち 5 ギガが使用されている) ため、GC の実行を遅らせるか、並列 foreach が終了するまで一時停止する (または実行頻度を下げるように構成する) 方法はありますか?

4

2 に答える 2

4

StockModel.Quotation クラスで許可されている場合は、プールを作成して、作成される新しいオブジェクトの数を制限できます。これは、ガベージ コレクターがレンダリングの途中で停止するのを防ぐために、ゲームで時々使用する手法です。

基本的なプールの実装は次のとおりです。

    class StockQuotationPool
    {

        private List<StockQuotation> poolItems;
        private volatile int itemsInPool;

        public StockQuotationPool(int poolSize)
        {
            this.poolItems = new List<StockQuotation>(poolSize);
            this.itemsInPool = poolSize;

        }

        public StockQuotation Create(string name, decimal value)
        {
            if (this.itemsInPool == 0)
            {
                // Block until new item ready - maybe use semaphore.
                throw new NotImplementedException();
            }

            // Items are in the pool, but no items have been created.
            if (this.poolItems.Count == 0)
            {
                this.itemsInPool--;
                return new StockQuotation(name, value);
            }

            // else, return one in the pool
            this.itemsInPool--;

            var item = this.poolItems[0];
            this.poolItems.Remove(item);

            item.Name = name;
            item.Value = value;

            return item;
        }

        public void Release(StockQuotation quote)
        {
            if (!this.poolItems.Contains(quote)
            {
                this.poolItems.Add(quote);
                this.itemsInPool++;
            }
        }

    } 

これは、StockQuotation が次のようになっていると仮定しています。

  class StockQuotation
    {
        internal StockQuotation(string name, decimal value)
        {
            this.Name = name;
            this.Value = value;
        }


        public string Name { get; set; }
        public decimal Value { get; set; }
    }

次に、新しい StockQuotation() コンストラクターを呼び出す代わりに、プールに新しいインスタンスを要求します。プールは既存のインスタンスを返し (必要に応じて事前に作成できます)、新しいインスタンスのように見えるようにすべてのプロパティを設定します。同時にスレッドを収容するのに十分な大きさのプール サイズが見つかるまで、いろいろ試す必要があるかもしれません。

スレッドから呼び出す方法は次のとおりです。

    // Get the pool, maybe from a singleton.
    var pool = new StockQuotationPool(100);


    var quote = pool.Create("test", 1.00m);


    try
    {
        // Work with quote

    }
    finally
    {
        pool.Release(quote);
    }

最後に、このクラスは現時点ではスレッド セーフではありません。そのようにするために何か助けが必要な場合はお知らせください。

于 2013-02-26T15:29:15.703 に答える
0

GCLatencyMode.LowLatency;ここで関連する質問を参照してください: Prevent .NET Garbage collection for short period of time

私は最近これを試みましたが、うまくいきませんでした。表示中のフォームにアイコン サイズのビットマップ イメージをキャッシュするときに、ガベージ コレクションがまだ呼び出されていました。私にとってうまくいったのは、AntsパフォーマンスプロファイラーとReflectorを使用して、GC.Collectを引き起こしている正確な呼び出しを見つけて回避することでした。

于 2013-02-19T21:17:46.133 に答える