ホームページ バックエンド開発 Golang Go に LRU キャッシュを実装する

Go に LRU キャッシュを実装する

Aug 05, 2024 pm 04:04 PM

Implement an LRU Cache in Go

したがって、小規模なキャッシュが必要ですが、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 サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

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

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

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

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

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

Golang vs. Python:パフォーマンスとスケーラビリティ Golang vs. Python:パフォーマンスとスケーラビリティ Apr 19, 2025 am 12:18 AM

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

Golang and C:Concurrency vs. Raw Speed Golang and C:Concurrency vs. Raw Speed Apr 21, 2025 am 12:16 AM

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

ゴーを始めましょう:初心者のガイド ゴーを始めましょう:初心者のガイド Apr 26, 2025 am 12:21 AM

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

Golang vs. C:パフォーマンスと速度の比較 Golang vs. C:パフォーマンスと速度の比較 Apr 21, 2025 am 12:13 AM

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

Golang vs. Python:重要な違​​いと類似点 Golang vs. Python:重要な違​​いと類似点 Apr 17, 2025 am 12:15 AM

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

GolangとC:パフォーマンスのトレードオフ GolangとC:パフォーマンスのトレードオフ Apr 17, 2025 am 12:18 AM

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

パフォーマンスレース:ゴラン対c パフォーマンスレース:ゴラン対c Apr 16, 2025 am 12:07 AM

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

Golang vs. Python:長所と短所 Golang vs. Python:長所と短所 Apr 21, 2025 am 12:17 AM

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

See all articles