目次
LRUとは何ですか
コンピュータ システムでは、LRU (Least Recent Used、最も最近使用されていない) はキャッシュ置換アルゴリズムです。キャッシュとは、コンピュータシステムにおいて高速にデータを取得できる媒体であり、キャッシュ置換アルゴリズムは、キャッシュスペースが不足した場合に、後からアクセスするデータにより多くのキャッシュスペースを確保できるように、キャッシュされたデータの一部を削除して空き容量を確保する必要があります。
LRUアルゴリズムでは、キャッシュ内のデータを「ホットデータ」と「コールドデータ」に分け、ホットデータは頻繁に使用されるデータ、コールドデータはほとんど使用されないデータとします。キャッシュがいっぱいで、新しいデータをキャッシュに追加する必要がある場合は、LRU アルゴリズムを通じてキャッシュから一部のデータを削除し、その後、長期間アクセスされていないデータ、つまり「コールド データ」を削除する必要があります。 「ホット データ」をキャッシュに保持します。「ホット データ」は使用される可能性が高いため、これによりキャッシュ ヒット率が効果的に向上し、メモリ使用量が削減され、システム パフォーマンスが最適化されます。
平たく言えば、最近頻繁にアクセスされたデータの保持率は高くなり、頻繁にアクセスされていないデータは排除されます。
LRUのコアアイデア
LRUの核となる考え方は「時間局所性原理」です。この原理は、一定期間内にプログラムによってアクセスされたデータは、近い将来アクセスされる可能性が高いため、キャッシュされる可能性が高いことを示しています。長期間アクセスされていないデータは、近い将来アクセスされなくなる可能性が高いため、消去される可能性が高くなります。この原則に基づいて、LRU アルゴリズムは、キャッシュが十分ではない場合、まずキャッシュ内のデータを長期間使用されなかったデータと置き換え、より頻繁に使用されるデータがキャッシュ内にあることを保証し、ヒット率を向上させます。キャッシュのレート。
具体的には、キャッシュされたデータの順序を維持するために、キャッシュ オブジェクト内でリンク リストが使用されます。キャッシュ オブジェクトが使用されるたびに、データはリンク リストからリンク リストの最後に移動されます。キャッシュ オブジェクトがいっぱいになるたびに、リンクされたリストの先頭のデータは不要に移動されます。
これにより、リンク リストの末尾のデータが最後に使用されたデータとなり、リンク リストの先頭のデータが長期間使用されていないことが保証されるため、キャッシュ内のデータの順序を維持できます。リンク リストのノードを移動することでデータを維持し、リンク リストの先頭のデータを削除することができます。キャッシュ ヒット率を向上させるという目的を達成するために、キャッシュ サイズが一定の制限以下であることを確認します。したがって、LRU アルゴリズムの中心的な考え方は、「長期間使用されていないデータを削除し、最も最近使用されていないデータを保持する」ことでシステムのパフォーマンスを最適化することです。
コード実装 1: 二重リンク リスト + ハッシュ テーブル
using System.Collections.Generic;
public class LRUCache<K, V>
{
private int capacity;
private Dictionary<K, LinkedListNode<Tuple<K, V>>> dict;
private LinkedList<Tuple<K, V>> linkedList;
public LRUCache(int capacity)
{
this.capacity = capacity;
this.dict = new Dictionary<K, LinkedListNode<Tuple<K, V>>>();
this.linkedList = new LinkedList<Tuple<K, V>>();
}
public V Get(K key)
{
if (!dict.ContainsKey(key))
{
return default(V);
}
var node = dict[key];
linkedList.Remove(node);
linkedList.AddLast(node);
return node.Value.Item2;
}
public void Put(K key, V value)
{
if (dict.ContainsKey(key))
{
var node = dict[key];
linkedList.Remove(node);
}
var newNode = new LinkedListNode<Tuple<K, V>>(Tuple.Create(key, value));
dict[key] = newNode;
linkedList.AddLast(newNode);
if (dict.Count > capacity)
{
var firstNode = linkedList.First;
linkedList.RemoveFirst();
dict.Remove(firstNode.Value.Item1);
}
}
}
// Usage:
var lruCache = new LRUCache<string, int>(2);
lruCache.Put("a", 1);
lruCache.Put("b", 2);
Console.WriteLine(lruCache.Get("a")); // Output: 1
lruCache.Put("c", 3);
Console.WriteLine(lruCache.Get("b")); // Output: 0 (not found)
分析する
二重リンク リスト (二重リンク リスト ノードには前のノードと次のノードへのポインターがあります) を使用すると、O(1) 回のノード削除を実現できます。ノードを削除するときは、その前のノードのポインタと次のノードのポインタを更新するだけで、このノードをリンク リストから削除できます。
コード実装 2: OrderedDictionary
using System.Collections.Specialized;
namespace Tools
{
public class LRUCache<K, V>
{
private OrderedDictionary dict;
private int capacity;
public LRUCache(int capacity)
{
this.capacity = capacity;
dict = new OrderedDictionary();
}
public V Pop(K key)
{
if (!dict.Contains(key))
{
return default(V);
}
var value = (V)dict[key];
dict.Remove(key);
dict.Add(key, value);
return value;
}
public void Push(K key, V value)
{
if (dict.Contains(key))
{
dict.Remove(key);
}
else if (dict.Count >= capacity)
{
dict.RemoveAt(0);
}
dict.Add(key, value);
}
}
}
分析する
OrderedDictionary は C# の組み込みデータ構造であり、キーと値のペアの順序付けられたコレクションであり、キーと値のペアを順番に取得して走査することをサポートします。
以前の実装と比較すると、コードが簡潔であり、使用されるデータ構造が 1 つだけであるという利点があります。デメリットとしては、作業効率が悪くなってしまうことです。
OrderedDictionary は Dictionary に似ていますが、次のような違いがあります。
- OrderedDictionary は、追加順にソートされたキーのリストを内部的に保持します。
- OrderedDictionary は内部で 2 つの ArrayList を使用します。1 つはキーの保存用で、もう 1 つはキーに関連付けられた値の保存用です。これは、キーと値のペアが取得または追加されるたびに追加の操作が必要となるため、OrderedDictionary は Dictionary ほど効率的ではないことを意味します。
プロジェクト事例
Unity 開発ではオブジェクト プールを使用することが多いため、LRU を使用してオブジェクト プールを最適化し、過度のメモリ使用を回避できます。
知らせ
次回は実戦に入り、LRUオブジェクトプールを実装していきます。
終わり
以前オブジェクトプールについて記事を書きましたが、あまり良くないので最適化を検討してみます。
Unity 学習メモ – オブジェクト プールを使用してゲーム オブジェクトをエレガントかつ簡単に生成する方法