キャッシュずしおのConcurrentDictionary

倚くの開発者は、倚くの堎合、デヌタベヌスからのみデヌタを受信するか、いく぀かのテヌブルのキャッシュを保持するずいうゞレンマに盎面したす。 基本的に、これらはレコヌドをほずんど含たないディレクトリであり、垞に手元に必芁です。 これはハリりッドの問題であり、この蚘事では察凊したせん。



.NET䞊のトランスポヌト監芖システムの負荷の高いサヌバヌを蚭蚈するずきに、同じ問題が目の前で発生したした。 その結果、キャッシュが必芁であるこずが決定されたした。 蟞曞キャッシュは、ConcurrentDictionaryのラッパヌに栌玍されるようになりたした。 このオプションは、スレッドセヌフなディクショナリ甚の暙準の.NETツヌルであるため、あたり調査するこずなく採甚されたした。 今こそ、この゜リュヌションのパフォヌマンスをテストするずきです。 これに぀いおは、実際には蚘事です。 たた、この蚘事の最埌には、ConcurrentDictionary内での配眮方法に関する少しの研究がありたす。







問題の声明



キヌず倀のペアのスレッドセヌフなコレクションが必芁です。これにより、次のこずが可胜になりたす。



  1. 識別子キヌを持぀オブゞェクトを芁求する-もちろん、最も頻繁に䜿甚されたす。
  2. 新しい芁玠を倉曎、削陀、远加するこずはたれですが、スレッドの安党性を確保する必芁がありたす。
  3. 倀の操䜜-すべおの倀を芁求し、倀オブゞェクトのプロパティで怜玢したす。


条項3は蟞曞コレクションでは䞀般的ではないため、この構成の䞻なブレヌキずなりたす。 ただし、ルックアップテヌブルのキャッシュを䜿甚する堎合、そのような状況は避けられたせんたずえば、線集のために管理パネルにディクショナリ党䜓を衚瀺するのは簡単です。



キャッシュが䜿甚されるさたざたなシステムを怜蚎しおみたしょう。 これらは、蟞曞を䜿甚した操䜜の頻床が異なりたす。 これらの操䜜のタむプを圢匏化したす。







テスト参加者のリスト



  1. ConcurrentDictionary 。 これは、スレッドセヌフが統合されたMicrosoftのタヌンキヌ゜リュヌションです。 䟿利なメ゜ッドTryGetValue、TryAdd、TryUpdate、AddOrUpdate、TryDeleteを実装したす。これにより、競合解決ポリシヌを簡単に蚭定できたす。 実装機胜に぀いおは、蚘事の最埌で説明したす。
  2. Monitorを介したロック付きの蟞曞 。 最も正面からの解決策は、すべおの非スレッドセヌフ操䜜がロック構造にラップされるこずです。
  3. ReaderWriterLockを介したロック付きの蟞曞 。 以前の゜リュヌションの最適化-操䜜は読み取り操䜜ず曞き蟌み操䜜に分けられたす。 したがっお、耇数のストリヌムを同時に読み取るこずができ、蚘録には排他的アクセスが必芁です。
  4. ReaderWriterLockSlimを介したロック付きの蟞曞 。 基本的に同じですが、新しいクラスを䜿甚したす再垰制埡蚭定を远加。 このタスクのコンテキストでは、ReaderWriterLock以倖を衚瀺する必芁はほずんどありたせん。
  5. Wintellect PowerThreadingラむブラリのOneManyResourceLockerを介したロック付き蟞曞 -Jeffrey RichterによるReaderWriterLockのトリッキヌな実装。 NuGetパッケヌゞではなく、公匏サむトのバヌゞョンが䜿甚されたこずを明確にしたす。バヌゞョンが異なるため、気に入らなかったためです。
  6. Hashtable.Synchronized 。 たた、Microsoftの既成の゜リュヌション-スレッドセヌフなむンデクサヌを提䟛したす。 非ゞェネリックコレクションボックス化、読みやすさの䜎䞋であり、Tryプレフィックスを持぀メ゜ッドがないため、䜿甚するのは䞍䟿です同時に远加/曎新するためのポリシヌを蚭定するこずは䞍可胜です。




ハンドラヌの実装方法を正確に簡朔に説明したす。



  1. GET操䜜すべおの参加者は、IDictionaryむンタヌフェむスからスレッドセヌフなTryGetValueメ゜ッドを䜿甚したす。 Hashtableの堎合、タむプコヌド化されたむンデクサヌが䜿甚されたす。
  2. ADD操䜜ConcurrentDictionaryの堎合-AddOrUpdate、Dictionaryの堎合-ロックの曞き蟌みずむンデクサヌの远加、Hashtableの堎合-ロックなしのむンデクサヌの远加。
  3. UPDATE操䜜ConcurrentDictionaryの堎合、最初にTryGetValue、次にTryUpdate。

    このメ゜ッドは、これら2぀のメ゜ッド間で䞊行曎新を実行できるずいう点で興味深いテスト䞭にも明らかになった。 この堎合、oldValueがTryUpdateに枡されるため、このたれなケヌスでは曞き換えが倱敗したす。 ディクショナリの堎合、ContainsKeyを介しお可甚性を確認し、成功した堎合は曞き蟌みロックを蚭定しお倀を䞊曞きしたす。 Hashtable甚の䟿利なTryUpdateがないため、キヌの存圚を確認する手間がかからず、远加の堎合のように、倀はむンデクサヌによっお䞊曞きされたすこのコレクションでは、これは重芁ではありたせん-ずにかくかなり悪かったです。
  4. 操䜜SEARCH ConcurrentDictionaryの堎合、LINQのFirstOrDefaultが䜿甚され、残りの堎合、同じFirstOrDefaultに察しお読み取りロックが䜿甚されたす。




詊隓台



テスト甚に、コン゜ヌルアプリケヌションが䜜成されたした リンク 。

  1. 特定のすべおのタむプの操䜜を凊理できる䞀連のハンドラヌが䜜成されたす。
  2. N個の芁玠のディクショナリが䜜成されたすデフォルトでは10,000。
  3. Mの量のさたざたなタむプのタスクのコレクションが䜜成されたすデフォルトでは10,000。
  4. 各ハンドラヌは、生成された蟞曞内のすべおのタスクすべおのハンドラヌに共通の䞊列凊理を実行したす。
  5. 実隓ポむント2〜4は、指定された回数デフォルトでは10回実行され、取埗された時間は平均されたす。 枬定は、Core 2 Quad 2.66 GHzず8 GBのメモリを搭茉したマシンで行われたした。


デフォルト倀は非垞に小さいですが、倀を増やしおも基本的には䜕も倉わりたせん。



詊隓結果



テストは、操䜜のタむプごずに異なる分散オプションを䜿甚しお実行され、テヌブルが倧きすぎるこずが刀明したした。党䜓をここで確認できたす リンク  わかりやすくするために、合蚈操䜜数の倀による読み取りの割合に応じおマむクロ秒単䜍でテスト実行時間のグラフを瀺したす曞き蟌み操䜜は20に固定され、残りはキヌによっお読み取られたす。







結論



すべおの参加者のパフォヌマンスは、曞き蟌み操䜜の回数に関係なく、倀ごずの読み取り回数に比䟋しお䜎䞋したす。



  1. ConcurrentDictionary 。 実隓により、このツヌルはこのタスクに最適であるこずが瀺されおいたす。 倀による読み取りはパフォヌマンスに倧きく圱響したすが、他の参加者よりも高速です。
  2. 蟞曞+モニタヌ 。 倧幅に遅い、期埅される結果。
  3. 蟞曞+ ReaderWriterLock 。 以前のバヌゞョンの最適化もすべお期埅されおいたす。

    蚘録操䜜が倚いほど、差は小さくなるこずに泚意しおください。 ある時点から、ブロックプロセス自䜓のオヌバヌヘッドが小さくなるため、Monitorがさらに奜たしい状態になりたす。
  4. 蟞曞+ ReaderWriterLockSlim 䜕らかの理由で、私は単玔なモニタヌでさえも䜕ずか倱いたした。 匷化された機胜以前のバヌゞョンず比范しおがパフォヌマンスに圱響したか、それを調理する方法がわかりたせん。
  5. 蟞曞+ OneManyResourceLock 。 リヒタヌは、読み取り/曞き蟌みロックからすべおを絞り出したようです。 テスト結果によるず、これは蟞曞の最速䜿甚です。 しかし、ConcurrentDictionaryはさらに高速です。
  6. ハッシュテヌブル 。 予想される倱敗。 おそらく私はそれを誀っお䜿甚したしたが、他の参加者に匹敵する結果を埗るこずができるずは思いたせん。 ずにかく、䞀般的でないコレクションを扱うのはどういうわけか気のめいるようでした。




内郚デバむスConcurrentDictionary



勝者を詳しく芋おみたしょう。぀たり、ConcurrentDictionaryの゜ヌスを芋おみたしょう。



このクラスを䜜成するず、 CapacityおよびConcurrencyLevelの 2぀のパラメヌタヌが蚭定されたす。 最初の Capacity はコレクションに銎染みがあり、内郚コレクションを展開せずに蚘録できる芁玠の数を蚭定したす。 この堎合、リンクされたリストが䜜成されたすm_buckets、バスケットず呌ばれたす デフォルト倀は31です。



2番目のパラメヌタヌ ConsurrencyLevel は、蟞曞に同時に曞き蟌むこずができるスレッドの数を決定したす。 これは、モニタヌによるロック甚のオブゞェクトのコレクションを䜜成するこずにより実珟されたす。 このような各ブロッキングオブゞェクトは、ほが同じ数のバスケットを担圓したす。 デフォルト倀はEnvironment.ProcessorCount * 4です。



したがっお、ディクショナリ内の各オブゞェクトは、それが存圚するバスケットず曞き蟌み甚のロックオブゞェクトに䞀意に関連付けられたす。 これは、次の方法で行われたす。



/// <summary> /// Computes the bucket and lock number for a particular key. /// </summary> private void GetBucketAndLockNo( int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount) { bucketNo = (hashcode & 0x7fffffff) % bucketCount; lockNo = bucketNo % lockCount; Assert(bucketNo >= 0 && bucketNo < bucketCount); Assert(lockNo >= 0 && lockNo < lockCount); }
      
      







奇劙なこずに、ConcurrencyLevel = 1であっおも、ConcurrentDictionaryは通垞のロック蟞曞よりも高速です。 たた、クラスがむテレヌタを介しお䜿甚するために最適化されおいるこずも泚目に倀したすテストが瀺したずおり。 特に、ToArrayメ゜ッドを呌び出すず、すべおのバスケットでロックが実行され、むテレヌタヌが比范的安䟡に䜿甚されたす。



䟋dictionary.Values.ToArrayよりもdictionary.Selectx => x.Value.ToArrayを䜿甚するこずをお勧めしたす。



この蚘事は、同瀟の䞀流開発者によっお曞かれたした。

ご枅聎ありがずうございたした



All Articles