リクエストページングシステムは、基本的なページングシステムに基づいており、仮想メモリ機能をサポートするために、リクエストページング機能とページ置換機能が追加されています。リクエストページングは、現在、仮想メモリを実装するために最も一般的に使用されている方法です。
要求ページングシステムでは、ジョブを開始できるように、現在必要なページの一部をメモリにロードするだけで済みます。ジョブの実行中に、アクセスするページがメモリにない場合は、彫刻機能を介して転送されます。同時に、一時的に使用されていないページは、置換機能を介して外部メモリにスワップアウトできます。メモリスペースを解放します。
ページ置換アルゴリズムの主な目標は、ページ置換の頻度(または最低のページフォールト率)を最小限に抑えることです。
一般的なページ置換アルゴリズムは次のとおりです。
1.最適な置換アルゴリズム(最適、OPT)
2.先入れ先出しページ置換アルゴリズム(先入れ先出し、FIFO)
3.最近使用されていない(LRU)置換アルゴリズム
4.使用頻度が最も低い(LFU)
5.クロックアルゴリズム(クロック)、改良されたクロックアルゴリズム1.最適な置換アルゴリズム(最適、OPT)
目次
2.先入れ先出しページ置換アルゴリズム(先入れ先出し、FIFO)
1.最適な置換アルゴリズム(最適、OPT)
スワップアウトされる選択されたページは、将来使用されることはなく、長期間アクセスされないため、通常、最低のページフォールト率が保証されます。ただし、ページにアクセスできない期間は予測できないため、このアルゴリズムは理論上のアルゴリズムです。最良の置換アルゴリズムを使用して、他のアルゴリズムを評価できます。
2.先入れ先出しページ置換アルゴリズム(先入れ先出し、FIFO)
スワップアウトするために選択されたページは、最初に入力されたページです。アルゴリズムの実装は簡単ですが、頻繁にアクセスされるページもスワップアウトされるため、ページフォールト率が高くなります。
3.最近使用されていない(LRU)置換アルゴリズム
今後使用するページの状況を知ることはできませんが、過去に使用したページの状況を知ることは可能です。LRUは、最も使用頻度の低いページをスワップアウトし、過去の期間に使用されていないページは近い将来アクセスされないと考えています。
実現方法1:
メモリ内のすべてのページのリンクリストを維持します。ページにアクセスしたら、そのページをリンクリストの先頭に移動します。これにより、リンクリストの最後にあるページが最も長い時間アクセスされていないページになります。
実装方法2:
各ページにアクセスフィールドを設定して、ページが最後にアクセスされてからの経過時間を記録します。ページが削除されると、既存のページの中で最大の値を持つページが選択されて削除されます。
4.使用頻度が最も低い(LFU)
これは、「最近の期間にデータがほとんど使用されていない場合、将来使用される可能性は低い」という考えに基づいています。
このアルゴリズムは、最新の期間で最も使用されていないページを削除されたページとして選択します。各ページのカウンターを構成します。ページにアクセスすると、そのカウンターの値が1ずつ増加します。ページ置換が必要な場合は、カウンター値が最小のページ、つまり番号が最小のページが選択されます。メモリ内のアクセスの数。削除されました。
このアルゴリズムで考えられる問題は、プロセスの開始時に何度もアクセスされるものもありますが、これらのページは将来アクセスされなくなる可能性があることです。このようなページは、メモリに長期間保持しないでください。この問題を解決する1つの方法は、タイマーを定期的に右に移動して、指数関数的に減衰する平均使用回数を形成することです。
LFUアルゴリズムとLRUアルゴリズムの違いに注意してください。LRUの除去ルールはアクセス時間に基づいていますが、LFUはアクセス数に基づいています。
5.ジッター
6. LRUの実装O(1)の実装
タイトルリンク:リンク:https://leetcode-cn.com/problems/lru-cache/
既知:
Javaデータ構造の時間計算量。
スタック
プッシュ:O(1)
ポップ:O(1)
スタックのトップ:O(1)
検索:O(n)
キュー/両端キュー/循環キュー
挿入:O(1)
削除:O(1)
長さを見つける:O(1)
ここで両端キューを選択できます
get操作の場合、最初にキーが存在するかどうかを判別します。キーが存在しない場合は、-1を返します。
キーが存在する場合は、ハッシュマップに従って値を返し、チームのトップに持ってきます。(addFirstがチームのヘッドであると仮定すると、両端キューはチームのヘッドとエンドを区別しません)
put操作の場合、最初にキーが存在するかどうかを判別します。キーが存在しない場合は、ハッシュマップして入力します。両端キューの先頭にキーを挿入します。
次に、二重リンクリストのノード数が容量を超えているかどうかを判断し、容量を超えている場合は、二重リンクリストのテールノードを削除し、ハッシュテーブルの対応するキーを削除します。
キーが存在する場合は、get操作と同様です。最初にハッシュテーブルを検索し、次に対応するノードの値をvalueに更新して、ノードをキューの先頭に移動します。
public class LRUCache {
private Map<Integer, Integer> cache = new HashMap<Integer, Integer>(); // 用来存放key-value对,cache缓存
private Deque<Integer> deque = new LinkedList<>(); // LRU算法用来淘汰最近最久未使用
private int capacity;
public LRUCache(int capacity) {
this.capacity = capacity;
// 使用伪头部和伪尾部节点
}
public int get(int key) {
if (!cache.containsKey(key)) return -1;
deque.remove(key); // 注意:deque里面remove(Object o) 如果是list,remove有多态要用:remove(Integer.valueOf(key))
deque.addFirst(key);
return cache.get(key); // 如果 key 存在,先通过哈希表定位
}
public void put(int key, int value) {
if (!cache.containsKey(key)) {
cache.put(key, value);
deque.addFirst(key);
if (deque.size() > capacity) {
Integer tmpNode1 = deque.removeLast();
cache.remove(tmpNode1);
}
} else {
cache.put(key, value);
deque.remove(key);
deque.addFirst(key);
}
}
}
get操作の場合、最初にキーが存在するかどうかを判別します。キーが存在しない場合は、-1を返します。
キーが存在する場合、そのキーに対応するノードが最後に使用されたノードです。ハッシュテーブルを介して二重リンクリスト内のノードの位置を見つけ、それを二重リンクリストの先頭に移動して、最後にノードの値を返します。
put操作の場合、最初にキーが存在するかどうかを判断します。キーが存在しない場合は、キーと値を使用して新しいノードを作成し、二重リンクリストの先頭にノードを追加し、キーとノードをハッシュに追加します。テーブル。
次に、二重リンクリストのノード数が容量を超えているかどうかを判断し、容量を超えている場合は、二重リンクリストのテールノードを削除し、ハッシュテーブルの対応する項目を削除します。
キーが存在する場合は、get操作と同様です。最初にハッシュテーブルを検索し、次に対応するノードの値をvalueに更新して、ノードを二重リンクリストの先頭に移動します。
class DLinkedNode {
int key;
int value;
DLinkedNode prev;
DLinkedNode next;
public DLinkedNode() {
}
public DLinkedNode(int _key, int _value) {
key = _key;
value = _value;
}
}
public class LRUCache {
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.capacity = capacity;
// 使用伪头部和伪尾部节点
head = new DLinkedNode();
tail = new DLinkedNode();
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkedNode node = cache.get(key);
if (node == null) {
return -1;
}
// 如果 key 存在,先通过哈希表定位,再移到头部
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkedNode node = cache.get(key);
if (node == null) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode newNode = new DLinkedNode(key, value);
// 添加进哈希表
cache.put(key, newNode);
// 添加至双向链表的头部
addToHead(newNode);
if (cache.size() > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode tail = removeTail();
// 删除哈希表中对应的项
cache.remove(tail.key);
}
} else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
node.value = value;
moveToHead(node);
}
}
private void addToHead(DLinkedNode node) {
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private void removeNode(DLinkedNode node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void moveToHead(DLinkedNode node) {
removeNode(node);
addToHead(node);
}
private DLinkedNode removeTail() {
DLinkedNode res = tail.prev;
removeNode(res);
return res;
}
}