この記事では、主に、次のように、高スループットとスレッドセーフのLRUキャッシュの関連コンテンツを研究しています。
数年前、キーワードのIDを見つけるためにLRUキャッシュを実装しました。必要なスループットは、多数のlocksとsynchronizedキーワードによって引き起こされるパフォーマンスの問題を排除するのに十分な大きさであるため、データ構造は非常に興味深いです。アプリケーションはJavaに実装されています。
一連の原子参照割り当ては、concurrenthashmapでLRUとLRUの順序を維持すると思いました。最初は、値をエントリに包みました。エントリには、ダブルリンクリストのLRUチェーンにノードがあります。チェーンのテールは最近使用されたエントリを維持し、ヘッドノードは、キャッシュが特定のサイズに達したときにクリアされる可能性のあるエントリを保存します。各ノードは、見つけるために使用されるエントリを指します。
キーを介して値を検索するとき、キャッシュは最初にマップを探して、この値が存在するかどうかを確認する必要があります。存在しない場合は、読み取りスルー方法でデータソースの値を読み取り、「欠落の場合は追加」のマップに追加することはローダーに依存します。高いスループットを確保するという課題は、LRUチェーンを効果的に維持することです。この同時ハッシュマップはセグメント化されており、スレッドレベルは特定のレベルにあります(マップを構築するときに同時性レベルを指定できます)は、スレッド競争があまり経験しません。しかし、LRUチェーンを同じように分割することはできませんか?この問題を解決するために、操作をクリアするための補助キューを導入しました。
キャッシュには6つの基本的な方法があります。キャッシュのヒットには、検索には2つの基本操作が含まれます。取得とオファーを提供します。粗損失には、Get、Load、Put、Offerの4つの基本的な方法があります。 PUTメソッドでは、明確な操作を追跡する必要がある場合があります。キャッシュがヒットすると、精製操作と呼ばれるLRUチェーンで受動的にクリアリングを行います。
取得:キーによるマップ内のルックアップエントリ
ロード:データソースからのロード値
PUT:エントリを作成し、キーにマップします
オファー:最近アクセスされたエントリを指すLRUリストのテールにノードを追加します
evict:リストのヘッドのノードと関連するエントリをマップから削除します(キャッシュが特定のサイズに達した後)
パージ:LRUリストで未使用のノードを削除します - これらのノードを穴と呼び、クリーンアップキューはこれらを追跡します
クリアリング操作と精製操作は、両方とも処理データの大きなバッチです。各操作の詳細を見てみましょう。
GET操作は次のように機能します:
get(k) - > vキーKによるルックアップエントリキャッシュがヒットした場合、エントリがありますeオファーエントリEを試してみてください。
キーが存在する場合、LRUチェーンのテールに新しいノードを提供して、これが最近使用されている値であることを示します。 Get and Offerの実行は原子操作ではないため(ここにはロックなし)、これが提供されたノードが最近使用されたエンティティにポイントを提供するとは言えませんが、同時に実行したときに取得した最近使用されたエンティティです。スレッド間で順序を実行することを強制し、申し出ません。これにより、スループットが制限される可能性があるためです。ノードを提供した後、値をクリアして返すためにいくつかの操作を実行しようとします。以下のオファーとパージ操作を詳細に見てみましょう。
キャッシュの損失が発生した場合、このキーのロード値をローダーに呼び出し、新しいエンティティを作成してマップに入れます。プット操作は次のとおりです。
put(e) - > e既存のエントリex <-map.putifabsent(ek、e)存在しない場合はエントリe;サイズが排出に達した場合、いくつかのエントリがエンドリターンエントリEを排除する場合、既存のエントリがありますEx Return Entry Ex Endがあります
ご覧のとおり、2つ以上のスレッドがエンティティをマップに入れたときに競争があるかもしれませんが、1つの成功しか許可されておらず、オファーが呼び出されます。 LRUチェーンのテールでノードを提供した後、キャッシュがそのしきい値に達したかどうかを確認する必要があります。これは、バッチクリア操作を開始するために使用する識別子です。この特定のアプリケーションシナリオでは、しきい値の設定は容量よりも小さくなります。清算操作は、すべてのエンティティが追加された場合ではなく、小さなバッチで発生します。複数のスレッドが、キャッシュ容量がその容量に達するまで、クリアリング操作に参加する場合があります。ロックは簡単ですが、スレッドは安全です。 LRUチェーンのヘッドノードをクリアするには、マップ内のマルチスレッド除去操作を避けるために慎重な原子操作が必要です。
このオファー操作は非常に興味深いです。常にノードを作成しようとしますが、LRUですぐに使用されなくなったノードを削除および削除しようとはしません。
テールノードがエントリEを参照しない場合、eを割り当てますeを割り当てますc <-en new node n(e)、新しいノード参照者、エントリEへの新しいノード参照eは、アトミック比較とセットノードenの場合はc、n node n add n node c not null set entry ceがnullにnode node conup end end end end que end que end que eding end end end end end end end end end end end end end end end end eding end coがあります。
まず、チェーンのテールのノードがアクセスされたエンティティを指していないことを確認します。これは、すべてのスレッドが同じキー価値ペアに頻繁にアクセスしない限り違いはありません。チェーンのテールに新しいノードが作成されます。このエンティティが異なる場合、新しいノードを提供する前に、エンティティの比較を行い、操作を設定しようとします。これにより、複数のスレッドが同じことを行うことができなくなります。
ノードを正常に割り当てるスレッドは、LRUチェーンの最後に新しいノードを提供します。この操作は、concurrentlinkedqueueの発見と同じです。依存関係アルゴリズムについては、次の記事で説明します。シンプル、高速、および実用的な非ブロッキングおよびブロッキング同時キューアルゴリズム。スレッドは、エンティティが以前に他のノードに関連しているかどうかを確認します。その場合、古いノードはすぐに削除されませんが、穴としてマークされます(そのエンティティへの参照は空に設定されます)
上記は、ハイスループットとスレッドセーフLRUキャッシュに関するこの記事のすべての詳細な説明であり、すべての人に役立つことを願っています。興味のある友人は、このサイトの他の関連トピックを引き続き参照できます。欠点がある場合は、それを指摘するためにメッセージを残してください。このサイトへのご支援をありがとうございました!