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
|
||||
|
||||
- [ ] 新建 `TlbEntry.cs`:`VPN -> PFN` 映射及有效位
|
||||
- [ ] 新建 `TlbCache.cs`:查询、更新、淘汰
|
||||
- [ ] 使用 `Dictionary + LinkedList` 实现 O(1) 级 LRU
|
||||
- [x] 新建 `TlbEntry.cs`:`VPN -> PFN` 映射及有效位
|
||||
- [x] 新建 `TlbCache.cs`:查询、更新、淘汰
|
||||
- [x] 使用 `Dictionary + LinkedList` 实现 O(1) 级 LRU
|
||||
|
||||
### 行为要求
|
||||
|
||||
- [ ] `Lookup(vpn)`:命中返回 PFN,未命中返回失败
|
||||
- [ ] `InsertOrUpdate(vpn, pfn)`:已存在则更新并置为最近使用
|
||||
- [ ] 超出容量时淘汰最久未使用项
|
||||
- [x] `Lookup(vpn)`:命中返回 PFN,未命中返回失败
|
||||
- [x] `InsertOrUpdate(vpn, pfn)`:已存在则更新并置为最近使用
|
||||
- [x] 超出容量时淘汰最久未使用项
|
||||
|
||||
### 完成标准
|
||||
|
||||
- [ ] 构造用例可验证 LRU 淘汰顺序正确
|
||||
- [ ] 连续访问时命中统计准确
|
||||
- [x] 构造用例可验证 LRU 淘汰顺序正确
|
||||
- [x] 连续访问时命中统计准确
|
||||
|
||||
## 5. Step 4 - 二级页表 + 缺页 + FIFO
|
||||
|
||||
|
|
@ -207,4 +207,4 @@
|
|||
|
||||
- [x] 先完成 Step 1(参数模型 + 校验)
|
||||
- [x] 紧接 Step 2(地址拆分)
|
||||
- [ ] 当天收尾前完成 Step 3(TLB LRU 最小可运行)
|
||||
- [x] 当天收尾前完成 Step 3(TLB LRU 最小可运行)
|
||||
|
|
|
|||
Reference in New Issue