Go に LRU キャッシュを実装する
したがって、小規模なキャッシュが必要ですが、Redis または memcached インスタンスを正当化することはできません。 Go でこれを実装するには何が必要かを見てみましょう。楽しみのために、プロジェクトで再利用できるようにジェネリックを使用して作成します。
LRU キャッシュは通常、固定容量と最も単純な排出ポリシーを持ち、アクセスされてから時間が最も長い要素を排出します。単純な lru キャッシュは次のインターフェイスを実装します:
type LRUCache[T any] interface { Get(key string) (value T, found bool) Put(key string, value T) Keys() []string Remove(key string) bool Clear() Capacity() int Len() int }
キャッシュは、何らかの値をキーとするエントリとしてデータ項目を保存することがわかっています。それは地図のように聞こえます。排除ポリシーの導入についてはどうですか?これを行う 1 つの方法は、各項目とともに timeAccessed プロパティを保持することです。次のようなもの:
type cacheEntry[T any] struct { Data T LastAccessed time.time }
ただし、パフォーマンスについて考えてみましょう。キャッシュ キーを検索できるだけでなく、必要に応じて最も古いものをできるだけ早く挿入および削除できるようにしたいと考えています。
ハッシュテーブルであるマップを使用すると、検索のパフォーマンスが非常に高速になります。最も古いエントリを見つけるにはどうすればよいでしょうか?キャッシュ構造体が次のようになっている場合:
type LRUCache[T any] { capacity int keyMap map[string]cacheEntry[T] }
エントリを削除する際には、必ずマップを反復処理して最も古いものを見つける必要があります。
キャッシュ エントリのリストを効率的に並べ替えて維持できる方法でエントリを保存する方法が必要です。ソートルーチンを使用する必要がないことが望ましいです。
二重リンク リストはこれを行う良い方法であり、実際に必要でない限り、エントリにアクセス時間を保存する必要はありません。そこで、ノード構造体とともに以下を実装するリンク リストがあると仮定しましょう:
type DoubleLinkedList[T any] interface { Head() *DoubleNode[T] Tail() *DoubleNode[T] // Append inserts new item at tail Append(data T) *DoubleNode[T] // Push appends new item at head Push(data T) *DoubleNode[T] Remove(node *DoubleNode[T]) *DoubleNode[T] RemoveTail() *DoubleNode[T] MoveToHead(node *DoubleNode[T]) } type DoubleNode[T any] struct { Data T Prev *DoubleNode[T] Next *DoubleNode[T] }
キャッシュ構造体は次のようになります。
type lruCache[T any] struct { capacity int keyMap map[string]*DoubleNode[lruCacheEntry[T]] list DoubleLinkedList[lruCacheEntry[T]] }
キャッシュ エントリの構造体は次のようになります:
type lruCacheEntry[T any] struct { key string value T }
現実的には、おそらくキャッシュ キーにインターフェイスを使用するでしょう。コードをシンプルにするために文字列を使用しています。
ここでの実装では、キャッシュ内で最近アクセスされたエントリが先頭にあり、最も最近使用されていないエントリが末尾になります。したがって、削除するときは、リンクされたリストの末尾要素を削除するだけです。
Get() 関数の実装は簡単です。
func (l *lruCache[T]) Get(key string) (value T, found bool) { if node, ok := l.keyMap[key]; ok { l.list.MoveToHead(node) return node.Data.value, ok } var zero T return zero, false }
Get では、キーのマップ エントリを取得し、ノードが「最近使用された」ノードとなるため、リストの先頭に移動するだけです。
Put() 関数は、必要に応じてエビクションを処理する場所です。
func (l *lruCache[T]) Put(key string, value T) { if node, ok := l.keyMap[key]; ok { node.Data = lruCacheEntry[T]{ key: key, value: value, } // move the element to the most recent position l.list.MoveToHead(node) } else { // insert the new element at the head newNode := l.list.Push(lruCacheEntry[T]{ key: key, value: value, }) l.keyMap[key] = newNode } // is eviction necessary if len(l.keyMap) > l.capacity { nodeRemoved := l.list.RemoveTail() delete(l.keyMap, nodeRemoved.Data.key) } }
Put() の場合、まず、指定されたキーの値がすでに存在するかどうかを確認します。存在する場合は、値を更新し、ノードをリストの先頭に移動します。それ以外の場合は、新しいキャッシュ エントリを作成し、それを先頭としてリストに追加し、マップに追加します。
最後に、容量を確認することを忘れないでください。新しいエントリが容量を超えた場合は、リストの最後尾である最も古いエントリを削除し、そのエントリをマップから削除します。
キャッシュ エントリの一部としてキーを保存すると、マップからキーを迅速に削除できることに注意してください。データをキャッシュ エントリにのみ保存した場合は、マップを反復処理してデータを見つける必要があります。
このキャッシュには、マルチスレッド アプリにとって重要な何かが欠けています。同期はありません。現実的には、キャッシュは複数のスレッドによってアクセスされます。同期は複雑なトピックです。私たちの実装では、キャッシュ構造体にミューテックスを追加できます:
type lruCache[T any] struct { capacity int keyMap map[string]DoubleNode[lruCacheEntry[T]] list DoubleLinkedList[lruCacheEntry[T]] mutex sync.RWMutex }
次に、各関数の先頭に次のコードを追加します。
l.mutex.Lock() defer l.mutex.Unlock()
読み取り/書き込みロックを使用していることに注意してください。一部の関数はキャッシュの構造を変更しないため、提供されている読み取りロック メソッド、たとえば Len() 関数を使用できます。
func (l *lruCache[T]) Len() int { l.mutex.RLock() defer l.mutex.RUnlock() return len(l.keyMap) }
キャッシュにアクセスしようとするスレッドが多数ある場合、ここで選択した同期戦略が失敗する可能性があることに注意してください。これは複雑なトピックであり、それ自体が一連の投稿になる可能性があります。
以下のリンクにあるリポジトリの完全な実装を参照してください。
キャッシュを実装するには何が違うでしょうか?同期についてはどのように対処しますか?これについてあなたの考えを聞きたいです。これに対する単一の解決策はありませんので、以下にコメントを書き込んでください。
ありがとうございます!
この投稿とこのシリーズのすべての投稿のコードはここにあります
以上がGo に LRU キャッシュを実装するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











Golangは、パフォーマンスとスケーラビリティの点でPythonよりも優れています。 1)Golangのコンピレーションタイプの特性と効率的な並行性モデルにより、高い並行性シナリオでうまく機能します。 2)Pythonは解釈された言語として、ゆっくりと実行されますが、Cythonなどのツールを介してパフォーマンスを最適化できます。

Golangは並行性がCよりも優れていますが、Cは生の速度ではGolangよりも優れています。 1)Golangは、GoroutineとChannelを通じて効率的な並行性を達成します。これは、多数の同時タスクの処理に適しています。 2)Cコンパイラの最適化と標準ライブラリを介して、極端な最適化を必要とするアプリケーションに適したハードウェアに近い高性能を提供します。

goisidealforforbeginnersandsutable forcloudnetworkservicesduetoitssimplicity、andconcurrencyfeatures.1)installgofromtheofficialwebsiteandverify with'goversion'.2)

Golangは迅速な発展と同時シナリオに適しており、Cは極端なパフォーマンスと低レベルの制御が必要なシナリオに適しています。 1)Golangは、ごみ収集と並行機関のメカニズムを通じてパフォーマンスを向上させ、高配列Webサービス開発に適しています。 2)Cは、手動のメモリ管理とコンパイラの最適化を通じて究極のパフォーマンスを実現し、埋め込みシステム開発に適しています。

GolangとPythonにはそれぞれ独自の利点があります。Golangは高性能と同時プログラミングに適していますが、PythonはデータサイエンスとWeb開発に適しています。 Golangは同時性モデルと効率的なパフォーマンスで知られていますが、Pythonは簡潔な構文とリッチライブラリエコシステムで知られています。

GolangとCのパフォーマンスの違いは、主にメモリ管理、コンピレーションの最適化、ランタイム効率に反映されています。 1)Golangのゴミ収集メカニズムは便利ですが、パフォーマンスに影響を与える可能性があります。

GolangとCにはそれぞれパフォーマンス競争において独自の利点があります。1)Golangは、高い並行性と迅速な発展に適しており、2)Cはより高いパフォーマンスと微細な制御を提供します。選択は、プロジェクトの要件とチームテクノロジースタックに基づいている必要があります。

GolangisidealforBuildingsCalables Systemsduetoitsefficiency andConcurrency、Whilepythonexcelsinquickscriptinganddataanalysisduetoitssimplicityand vastecosystem.golang'ssignencouragesclean、readisinediteNeditinesinedinediseNabletinedinedinedisedisedioncourase
