23

関連する簡単な情報:

AFAIK、並行スタック、キュー、およびバッグクラスは、リンクリストを使用して内部的に実装されます。
また、各スレッドが独自のリンクリストを担当するため、競合がはるかに少ないことを私は知っています。とにかく、私の質問はConcurrentDictionary<,>

しかし、私はこのコードをテストしていました:(シングルスレッド)

Stopwatch sw = new Stopwatch();
sw.Start();

    var d = new ConcurrentDictionary < int,  int > ();
    for(int i = 0; i < 1000000; i++) d[i] = 123;
    for(int i = 1000000; i < 2000000; i++) d[i] = 123;
    for(int i = 2000000; i < 3000000; i++) d[i] = 123;
    Console.WriteLine("baseline = " + sw.Elapsed);

sw.Restart();

    var d2 = new Dictionary < int, int > ();
    for(int i = 0; i < 1000000; i++)         lock (d2) d2[i] = 123;
    for(int i = 1000000; i < 2000000; i++)   lock (d2) d2[i] = 123;
    for(int i = 2000000; i < 3000000; i++)   lock (d2) d2[i] = 123;
    Console.WriteLine("baseline = " + sw.Elapsed);

sw.Stop();

結果:(何度もテスト、同じ値(+/-))。

baseline = 00:00:01.2604656
baseline = 00:00:00.3229741

質問 :

シングルスレッド環境でConcurrentDictionary<,> はるかに遅くなるのですか?

私の最初の本能は、それlock(){}が常に遅くなるということです。しかし、明らかにそうではありません。

4

8 に答える 8

30

まあ、それは複数のスレッドで使用できる可能性ConcurrentDictionaryを考慮に入れています。複数のスレッドからのアクセスを心配せずに逃げることができると想定するものよりも、内部のハウスキーピングが必要になることは、私には完全に合理的だと思います。逆の方法でうまくいったとしたら、私は非常に驚いていました。より安全なバージョンの方が常に高速だったとしたら、なぜ安全性の低いバージョンを使用するのでしょうか。

于 2013-03-06T16:04:38.867 に答える
28

同じ操作ConcurrentDictionaryよりも単純にオーバーヘッドが増える可能性が最も高い理由です。Dictionaryソースを掘り下げると、これは明らかに真実です

  • インデクサーにロックを使用します
  • 揮発性書き込みを使用します
  • .Netでアトミックであることが保証されていない値のアトミック書き込みを行う必要があります
  • コア追加ルーチンに追加の分岐があります (ロックを取得するか、アトミック書き込みを行うか)。

これらのコストはすべて、使用されているスレッドの数に関係なく発生します。これらのコストは個別には小さいかもしれませんが、無料ではなく、時間の経過とともに加算されます

于 2013-03-06T16:05:49.850 に答える
8

.NET 5 の更新:以前の回答はまだ古いランタイムに関連しているため、そのままにしておきますが、.NET 5は、結果に見られるように、通常よりもConcurrentDictionary読み取りが実際に高速になるまでさらに改善されているようです。以下 (COW は私のもので、以下で詳しく説明します)。これであなたが望むものを作ってください:)TryGetValue()DictionaryCopyOnWriteDictionary

|          Method |        Mean |     Error |    StdDev |    Gen 0 |    Gen 1 |    Gen 2 | Allocated |
|---------------- |------------:|----------:|----------:|---------:|---------:|---------:|----------:|
| ConcurrentWrite | 1,372.32 us | 12.752 us | 11.304 us | 226.5625 |  89.8438 |  44.9219 | 1398736 B |
|        COWWrite | 1,077.39 us | 21.435 us | 31.419 us |  56.6406 |  19.5313 |  11.7188 |  868629 B |
|       DictWrite |   347.19 us |  5.875 us |  5.208 us | 124.5117 | 124.5117 | 124.5117 |  673064 B |
|  ConcurrentRead |    63.53 us |  0.486 us |  0.431 us |        - |        - |        - |         - |
|         COWRead |    81.55 us |  0.908 us |  0.805 us |        - |        - |        - |         - |
|        DictRead |    70.71 us |  0.471 us |  0.393 us |        - |        - |        - |         - |

以前の回答、< .NET 5に引き続き関連:

この回答を最初に投稿して以来、の最新バージョンはConcurrentDictionary大幅に改善されています。読み取り時にロックされなくなったCopyOnWriteDictionaryため、より多くの機能を備えた実装とほぼ同じパフォーマンス プロファイルが提供されるため、ほとんどの場合、代わりにそれを使用することをお勧めします。またはConcurrentDictionaryよりも 20 ~ 30% オーバーヘッドが大きいため、パフォーマンスが重要なアプリケーションでは、それでも使用することでメリットが得られる可能性があります。DictionaryCopyOnWriteDictionary

私のロックフリーのスレッドセーフなコピーオンライト辞書の実装については、ここで読むことができます:

http://www.singulink.com/CodeIndex/post/fastest-thread-safe-lock-free-dictionary

永続的なキャッシュとして使用することを意図しているため、現在は追加のみ (値を置き換える機能あり) です。削除が必要な場合は、使用することをお勧めします。これを追加すると、追加されたロックによるパフォーマンスの向上がすべて排除ConcurrentDictionaryされるためです。CopyOnWriteDictionary

CopyOnWriteDictionary書き込みのバーストに対して非常に高速であり、ルックアップは通常、Dictionaryロックなしでほぼ​​標準の速度で実行されます。たまに書き、頻繁に読む場合は、これが最速のオプションです。

私の実装では、ディクショナリが更新されていない通常の状況で読み取りロックの必要性をなくすことで、最大の読み取りパフォーマンスを提供します。トレードオフは、更新が適用された後に辞書をコピーして交換する必要があることです (これはバックグラウンド スレッドで行われます) が、頻繁に書き込まない場合、または初期化中に 1 回だけ書き込む場合、トレードオフは間違いなく価値があります。それ。

于 2018-11-13T08:46:45.237 に答える
2

は、ConcurrentDictionary<>作成時にロックオブジェクトの内部セットを作成します(これはconcurrencyLevel、他の要因の中でも特に決定されます)-このロックオブジェクトのセットは、一連のきめ細かいロックの内部バケット構造へのアクセスを制御するために使用されます。

シングルスレッドのシナリオでは、ロックは必要ないため、これらのロックを取得および解放するための余分なオーバーヘッドが、おそらくあなたが見ている違いの原因です。

于 2013-03-06T16:05:21.473 に答える
2

1 つのスレッドで ConcurrentDictionary を使用したり、すべてが 1 つのスレッドで行われる場合はアクセスを同期しても意味がありません。もちろん、辞書は ConcrurrentDictionary を打ち負かします。

使用パターンとスレッド数に大きく依存します。これは、 ConcurrentDictionary がディクショナリとスレッド数の増加に伴うロックよりも優れていることを示すテストです。

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace ConsoleApp
{

    class Program
    {

        static void Main(string[] args)
        {
            Run(1, 100000, 10);
            Run(10, 100000, 10);
            Run(100, 100000, 10);
            Run(1000, 100000, 10);
            Console.ReadKey();
        }

        static void Run(int threads, int count, int cycles)
        {
            Console.WriteLine("");
            Console.WriteLine($"Threads: {threads}, items: {count}, cycles:{cycles}");

            var semaphore = new SemaphoreSlim(0, threads);

            var concurrentDictionary = new ConcurrentDictionary<int, string>();

            for (int i = 0; i < threads; i++)
            {
                Thread t = new Thread(() => Run(concurrentDictionary, count, cycles,  semaphore));
                t.Start();
            }

            Thread.Sleep(1000);

            var w = Stopwatch.StartNew();

            semaphore.Release(threads);

            for (int i = 0; i < threads; i++)
                semaphore.Wait();

            Console.WriteLine($"ConcurrentDictionary: {w.Elapsed}");

            var dictionary = new Dictionary<int, string>();
            for (int i = 0; i < threads; i++)
            {
                Thread t = new Thread(() => Run(dictionary, count, cycles, semaphore));
                t.Start();
            }

            Thread.Sleep(1000);

            w.Restart();

            semaphore.Release(threads);


            for (int i = 0; i < threads; i++)
                semaphore.Wait();

            Console.WriteLine($"Dictionary: {w.Elapsed}");

        }

        static void Run(ConcurrentDictionary<int, string> dic, int elements, int cycles, SemaphoreSlim semaphore)
        {
            semaphore.Wait();
            try
            {
                for (int i = 0; i < cycles; i++)
                    for (int j = 0; j < elements; j++)
                    {
                        var x = dic.GetOrAdd(i, x => x.ToString());
                    }
            }
            finally
            {
                semaphore.Release();
            }
        }

        static void Run(Dictionary<int, string> dic, int elements, int cycles, SemaphoreSlim semaphore)
        {
            semaphore.Wait();
            try
            {
                for (int i = 0; i < cycles; i++)
                    for (int j = 0; j < elements; j++)
                        lock (dic)
                        {
                            if (!dic.TryGetValue(i, out string value))
                                dic[i] = i.ToString();
                        }
            }
            finally
            {
                semaphore.Release();
            }
        }
    }
}

スレッド: 1、アイテム: 100000、サイクル: 10 ConcurrentDictionary: 00:00:00.0000499 辞書: 00:00:00.0000137

スレッド: 10、アイテム: 100000、サイクル: 10 ConcurrentDictionary: 00:00:00.0497413 Dictionary: 00:00:00.2638265

スレッド: 100、アイテム: 100000、サイクル: 10 ConcurrentDictionary: 00:00:00.2408781 Dictionary: 00:00:02.2257736

スレッド: 1000、アイテム: 100000、サイクル: 10 ConcurrentDictionary: 00:00:01.8196668 Dictionary: 00:00:25.5717232

于 2020-04-02T09:24:18.410 に答える
-7

あなたのテストは間違っています: の前にストップウォッチを停止する必要があります!

        Stopwatch sw = new Stopwatch();      
        sw.Start();
        var d = new ConcurrentDictionary<int, int>();
        for (int i = 0; i < 1000000; i++) d[i] = 123;
        for (int i = 1000000; i < 2000000; i++) d[i] = 123;
        for (int i = 2000000; i < 3000000; i++) d[i] = 123;
        sw.Stop();
        Console.WriteLine("baseline = " + sw.Elapsed);



        sw.Start();
        var d2 = new Dictionary<int, int>();
        for (int i = 0; i < 1000000; i++) lock (d2) d2[i] = 123;
        for (int i = 1000000; i < 2000000; i++) lock (d2) d2[i] = 123;
        for (int i = 2000000; i < 3000000; i++) lock (d2) d2[i] = 123;
        sw.Stop();
        Console.WriteLine("baseline = " + sw.Elapsed);

        sw.Stop();

--出力:

于 2015-06-17T14:16:59.250 に答える