Step 3 - TLB(全相联 + LRU)
This commit is contained in:
parent
4ab93d7806
commit
bda18464c8
|
|
@ -0,0 +1,121 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace VMdemo.Simulation
|
||||||
|
{
|
||||||
|
public class TlbCache
|
||||||
|
{
|
||||||
|
private readonly Dictionary<ulong, LinkedListNode<TlbEntry>> _index;
|
||||||
|
private readonly LinkedList<TlbEntry> _lruList;
|
||||||
|
|
||||||
|
public TlbCache(int capacity)
|
||||||
|
{
|
||||||
|
if (capacity <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(capacity), "TLB 容量必须是正整数。");
|
||||||
|
}
|
||||||
|
|
||||||
|
Capacity = capacity;
|
||||||
|
_index = new Dictionary<ulong, LinkedListNode<TlbEntry>>(capacity);
|
||||||
|
_lruList = new LinkedList<TlbEntry>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Capacity { get; }
|
||||||
|
public int Count => _index.Count;
|
||||||
|
public int HitCount { get; private set; }
|
||||||
|
public int MissCount { get; private set; }
|
||||||
|
|
||||||
|
public bool Lookup(ulong vpn, out ulong pfn)
|
||||||
|
{
|
||||||
|
return TryLookup(vpn, out pfn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryLookup(ulong vpn, out ulong pfn)
|
||||||
|
{
|
||||||
|
if (_index.TryGetValue(vpn, out var node) && node.Value.IsValid)
|
||||||
|
{
|
||||||
|
MoveToMostRecent(node);
|
||||||
|
pfn = node.Value.Pfn;
|
||||||
|
HitCount++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pfn = 0UL;
|
||||||
|
MissCount++;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InsertOrUpdate(ulong vpn, ulong pfn)
|
||||||
|
{
|
||||||
|
if (_index.TryGetValue(vpn, out var existingNode))
|
||||||
|
{
|
||||||
|
existingNode.Value = existingNode.Value.WithPfn(pfn);
|
||||||
|
MoveToMostRecent(existingNode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newNode = new LinkedListNode<TlbEntry>(new TlbEntry(vpn, pfn));
|
||||||
|
_lruList.AddFirst(newNode);
|
||||||
|
_index[vpn] = newNode;
|
||||||
|
|
||||||
|
if (_index.Count > Capacity)
|
||||||
|
{
|
||||||
|
EvictLeastRecent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(ulong vpn)
|
||||||
|
{
|
||||||
|
if (!_index.TryGetValue(vpn, out var node))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lruList.Remove(node);
|
||||||
|
_index.Remove(vpn);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_lruList.Clear();
|
||||||
|
_index.Clear();
|
||||||
|
HitCount = 0;
|
||||||
|
MissCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<TlbEntry> GetEntriesMostRecentFirst()
|
||||||
|
{
|
||||||
|
var snapshot = new List<TlbEntry>(_index.Count);
|
||||||
|
for (var node = _lruList.First; node != null; node = node.Next)
|
||||||
|
{
|
||||||
|
snapshot.Add(node.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MoveToMostRecent(LinkedListNode<TlbEntry> node)
|
||||||
|
{
|
||||||
|
if (node.List != _lruList || _lruList.First == node)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lruList.Remove(node);
|
||||||
|
_lruList.AddFirst(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EvictLeastRecent()
|
||||||
|
{
|
||||||
|
var leastRecentNode = _lruList.Last;
|
||||||
|
if (leastRecentNode == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lruList.RemoveLast();
|
||||||
|
_index.Remove(leastRecentNode.Value.Vpn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 80f194356d342d24d8a3a8bfc9f22599
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace VMdemo.Simulation
|
||||||
|
{
|
||||||
|
[Serializable]
|
||||||
|
public readonly struct TlbEntry
|
||||||
|
{
|
||||||
|
public TlbEntry(ulong vpn, ulong pfn, bool isValid = true)
|
||||||
|
{
|
||||||
|
Vpn = vpn;
|
||||||
|
Pfn = pfn;
|
||||||
|
IsValid = isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong Vpn { get; }
|
||||||
|
public ulong Pfn { get; }
|
||||||
|
public bool IsValid { get; }
|
||||||
|
|
||||||
|
public TlbEntry WithPfn(ulong pfn)
|
||||||
|
{
|
||||||
|
return new TlbEntry(Vpn, pfn, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"VPN={Vpn}, PFN={Pfn}, Valid={IsValid}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fc1daa201fa9e844a83e62ff9feb5ae6
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
using NUnit.Framework;
|
||||||
|
using VMdemo.Simulation;
|
||||||
|
|
||||||
|
namespace VMdemo.Tests.EditMode
|
||||||
|
{
|
||||||
|
public class Step3TlbCacheTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void Constructor_CapacityLessThanOne_Throws()
|
||||||
|
{
|
||||||
|
Assert.Throws<System.ArgumentOutOfRangeException>(() => new TlbCache(0));
|
||||||
|
Assert.Throws<System.ArgumentOutOfRangeException>(() => new TlbCache(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TryLookup_Miss_ReturnsFalseAndCountsMiss()
|
||||||
|
{
|
||||||
|
var cache = new TlbCache(2);
|
||||||
|
|
||||||
|
var hit = cache.TryLookup(10UL, out var pfn);
|
||||||
|
|
||||||
|
Assert.IsFalse(hit);
|
||||||
|
Assert.That(pfn, Is.EqualTo(0UL));
|
||||||
|
Assert.That(cache.HitCount, Is.EqualTo(0));
|
||||||
|
Assert.That(cache.MissCount, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void InsertOrUpdate_AndLookup_HitReturnsMappedPfn()
|
||||||
|
{
|
||||||
|
var cache = new TlbCache(2);
|
||||||
|
cache.InsertOrUpdate(10UL, 100UL);
|
||||||
|
|
||||||
|
var hit = cache.TryLookup(10UL, out var pfn);
|
||||||
|
|
||||||
|
Assert.IsTrue(hit);
|
||||||
|
Assert.That(pfn, Is.EqualTo(100UL));
|
||||||
|
Assert.That(cache.HitCount, Is.EqualTo(1));
|
||||||
|
Assert.That(cache.MissCount, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void InsertOrUpdate_ExistingEntry_UpdatesPfnAndMovesToMostRecent()
|
||||||
|
{
|
||||||
|
var cache = new TlbCache(3);
|
||||||
|
cache.InsertOrUpdate(1UL, 11UL);
|
||||||
|
cache.InsertOrUpdate(2UL, 22UL);
|
||||||
|
cache.InsertOrUpdate(3UL, 33UL);
|
||||||
|
|
||||||
|
cache.InsertOrUpdate(1UL, 111UL);
|
||||||
|
|
||||||
|
var entries = cache.GetEntriesMostRecentFirst();
|
||||||
|
|
||||||
|
Assert.That(entries.Count, Is.EqualTo(3));
|
||||||
|
Assert.That(entries[0].Vpn, Is.EqualTo(1UL));
|
||||||
|
Assert.That(entries[0].Pfn, Is.EqualTo(111UL));
|
||||||
|
Assert.That(entries[1].Vpn, Is.EqualTo(3UL));
|
||||||
|
Assert.That(entries[2].Vpn, Is.EqualTo(2UL));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void OverCapacity_EvictsLeastRecentlyUsedEntry()
|
||||||
|
{
|
||||||
|
var cache = new TlbCache(3);
|
||||||
|
cache.InsertOrUpdate(1UL, 11UL);
|
||||||
|
cache.InsertOrUpdate(2UL, 22UL);
|
||||||
|
cache.InsertOrUpdate(3UL, 33UL);
|
||||||
|
|
||||||
|
cache.TryLookup(1UL, out _); // refresh 1 as MRU
|
||||||
|
cache.InsertOrUpdate(4UL, 44UL); // should evict 2
|
||||||
|
|
||||||
|
Assert.That(cache.Count, Is.EqualTo(3));
|
||||||
|
Assert.IsFalse(cache.TryLookup(2UL, out _));
|
||||||
|
Assert.IsTrue(cache.TryLookup(1UL, out var pfn1));
|
||||||
|
Assert.That(pfn1, Is.EqualTo(11UL));
|
||||||
|
|
||||||
|
var entries = cache.GetEntriesMostRecentFirst();
|
||||||
|
Assert.That(entries[0].Vpn, Is.EqualTo(1UL));
|
||||||
|
Assert.That(entries[1].Vpn, Is.EqualTo(4UL));
|
||||||
|
Assert.That(entries[2].Vpn, Is.EqualTo(3UL));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Remove_ExistingAndMissingKeys_BehaveAsExpected()
|
||||||
|
{
|
||||||
|
var cache = new TlbCache(2);
|
||||||
|
cache.InsertOrUpdate(5UL, 55UL);
|
||||||
|
|
||||||
|
var removedFirst = cache.Remove(5UL);
|
||||||
|
var removedSecond = cache.Remove(5UL);
|
||||||
|
|
||||||
|
Assert.IsTrue(removedFirst);
|
||||||
|
Assert.IsFalse(removedSecond);
|
||||||
|
Assert.That(cache.Count, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Clear_ResetsEntriesAndCounters()
|
||||||
|
{
|
||||||
|
var cache = new TlbCache(2);
|
||||||
|
cache.InsertOrUpdate(1UL, 10UL);
|
||||||
|
cache.InsertOrUpdate(2UL, 20UL);
|
||||||
|
cache.TryLookup(1UL, out _);
|
||||||
|
cache.TryLookup(99UL, out _);
|
||||||
|
|
||||||
|
cache.Clear();
|
||||||
|
|
||||||
|
Assert.That(cache.Count, Is.EqualTo(0));
|
||||||
|
Assert.That(cache.HitCount, Is.EqualTo(0));
|
||||||
|
Assert.That(cache.MissCount, Is.EqualTo(0));
|
||||||
|
Assert.That(cache.GetEntriesMostRecentFirst().Count, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ContinuousLookup_TracksHitAndMissCountsAccurately()
|
||||||
|
{
|
||||||
|
var cache = new TlbCache(2);
|
||||||
|
cache.InsertOrUpdate(1UL, 10UL);
|
||||||
|
cache.InsertOrUpdate(2UL, 20UL);
|
||||||
|
|
||||||
|
cache.TryLookup(1UL, out _); // hit
|
||||||
|
cache.TryLookup(3UL, out _); // miss
|
||||||
|
cache.TryLookup(2UL, out _); // hit
|
||||||
|
cache.TryLookup(3UL, out _); // miss
|
||||||
|
|
||||||
|
Assert.That(cache.HitCount, Is.EqualTo(2));
|
||||||
|
Assert.That(cache.MissCount, Is.EqualTo(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 971f2b92f0eedec46b9cfb3f60199340
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -83,20 +83,20 @@
|
||||||
|
|
||||||
### TODO
|
### TODO
|
||||||
|
|
||||||
- [ ] 新建 `TlbEntry.cs`:`VPN -> PFN` 映射及有效位
|
- [x] 新建 `TlbEntry.cs`:`VPN -> PFN` 映射及有效位
|
||||||
- [ ] 新建 `TlbCache.cs`:查询、更新、淘汰
|
- [x] 新建 `TlbCache.cs`:查询、更新、淘汰
|
||||||
- [ ] 使用 `Dictionary + LinkedList` 实现 O(1) 级 LRU
|
- [x] 使用 `Dictionary + LinkedList` 实现 O(1) 级 LRU
|
||||||
|
|
||||||
### 行为要求
|
### 行为要求
|
||||||
|
|
||||||
- [ ] `Lookup(vpn)`:命中返回 PFN,未命中返回失败
|
- [x] `Lookup(vpn)`:命中返回 PFN,未命中返回失败
|
||||||
- [ ] `InsertOrUpdate(vpn, pfn)`:已存在则更新并置为最近使用
|
- [x] `InsertOrUpdate(vpn, pfn)`:已存在则更新并置为最近使用
|
||||||
- [ ] 超出容量时淘汰最久未使用项
|
- [x] 超出容量时淘汰最久未使用项
|
||||||
|
|
||||||
### 完成标准
|
### 完成标准
|
||||||
|
|
||||||
- [ ] 构造用例可验证 LRU 淘汰顺序正确
|
- [x] 构造用例可验证 LRU 淘汰顺序正确
|
||||||
- [ ] 连续访问时命中统计准确
|
- [x] 连续访问时命中统计准确
|
||||||
|
|
||||||
## 5. Step 4 - 二级页表 + 缺页 + FIFO
|
## 5. Step 4 - 二级页表 + 缺页 + FIFO
|
||||||
|
|
||||||
|
|
@ -207,4 +207,4 @@
|
||||||
|
|
||||||
- [x] 先完成 Step 1(参数模型 + 校验)
|
- [x] 先完成 Step 1(参数模型 + 校验)
|
||||||
- [x] 紧接 Step 2(地址拆分)
|
- [x] 紧接 Step 2(地址拆分)
|
||||||
- [ ] 当天收尾前完成 Step 3(TLB LRU 最小可运行)
|
- [x] 当天收尾前完成 Step 3(TLB LRU 最小可运行)
|
||||||
|
|
|
||||||
Reference in New Issue