From 785e92ec8b7d4ba559d92a959bb25d89303b36ca Mon Sep 17 00:00:00 2001 From: SepComet <2428390463@qq.com> Date: Tue, 14 Apr 2026 09:11:55 +0800 Subject: [PATCH] =?UTF-8?q?Step=206=20-=20=E7=BB=9F=E8=AE=A1=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Scripts/Simulation/StatsCollector.cs | 77 ++++++++++ .../Scripts/Simulation/StatsCollector.cs.meta | 11 ++ Assets/Scripts/Simulation/TranslatorEngine.cs | 14 ++ .../EditMode/Step6StatsCollectorTests.cs | 135 ++++++++++++++++++ .../EditMode/Step6StatsCollectorTests.cs.meta | 11 ++ doc/MVP-TODO.md | 16 +-- 6 files changed, 256 insertions(+), 8 deletions(-) create mode 100644 Assets/Scripts/Simulation/StatsCollector.cs create mode 100644 Assets/Scripts/Simulation/StatsCollector.cs.meta create mode 100644 Assets/Tests/EditMode/Step6StatsCollectorTests.cs create mode 100644 Assets/Tests/EditMode/Step6StatsCollectorTests.cs.meta diff --git a/Assets/Scripts/Simulation/StatsCollector.cs b/Assets/Scripts/Simulation/StatsCollector.cs new file mode 100644 index 0000000..68b9711 --- /dev/null +++ b/Assets/Scripts/Simulation/StatsCollector.cs @@ -0,0 +1,77 @@ +using System; + +namespace VMdemo.Simulation +{ + public class StatsCollector + { + public int TotalAccess { get; private set; } + public int TlbHit { get; private set; } + public int PageTableHit { get; private set; } + public int PageFault { get; private set; } + public long TotalCost { get; private set; } + + public double TlbHitRate => TotalAccess > 0 ? (double)TlbHit / TotalAccess : 0.0; + public double PageTableHitRate => TotalAccess > 0 ? (double)PageTableHit / TotalAccess : 0.0; + public double AvgCost => TotalAccess > 0 ? (double)TotalCost / TotalAccess : 0.0; + + public void Reset() + { + TotalAccess = 0; + TlbHit = 0; + PageTableHit = 0; + PageFault = 0; + TotalCost = 0L; + } + + public int CalculateCost(bool tlbHit, bool pageTableHit, bool pageFault, int pageFaultPenalty) + { + if (pageFaultPenalty <= 0) + { + throw new ArgumentOutOfRangeException(nameof(pageFaultPenalty), "pageFaultPenalty 必须是正整数。"); + } + + if (tlbHit) + { + return 1; + } + + if (pageFault) + { + return 4 + pageFaultPenalty; + } + + if (pageTableHit) + { + return 4; + } + + throw new InvalidOperationException("访问结果无效:TLB miss 时必须是页表命中或缺页之一。"); + } + + public void RecordAccess(bool tlbHit, bool pageTableHit, bool pageFault, int cost) + { + if (cost < 0) + { + throw new ArgumentOutOfRangeException(nameof(cost), "cost 不能为负数。"); + } + + TotalAccess++; + if (tlbHit) + { + TlbHit++; + } + + if (pageTableHit) + { + PageTableHit++; + } + + if (pageFault) + { + PageFault++; + } + + TotalCost += cost; + } + } +} diff --git a/Assets/Scripts/Simulation/StatsCollector.cs.meta b/Assets/Scripts/Simulation/StatsCollector.cs.meta new file mode 100644 index 0000000..eca7cbc --- /dev/null +++ b/Assets/Scripts/Simulation/StatsCollector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5fc1bac53447fa34db019e4b6ed2d26c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Simulation/TranslatorEngine.cs b/Assets/Scripts/Simulation/TranslatorEngine.cs index 10c0133..4cb6be4 100644 --- a/Assets/Scripts/Simulation/TranslatorEngine.cs +++ b/Assets/Scripts/Simulation/TranslatorEngine.cs @@ -75,6 +75,7 @@ namespace VMdemo.Simulation _derivedConfig = derived; _seed = seed; State = new SimulationState(); + Stats = new StatsCollector(); AddressTranslatorUtils.GetPageTableBitLayout(derived.VpnBits, out var l1Bits, out var l2Bits); L1Bits = l1Bits; @@ -97,6 +98,7 @@ namespace VMdemo.Simulation public TlbCache TlbCache => _tlbCache; public TwoLevelPageTable PageTable => _pageTable; public PhysicalMemoryManager PhysicalMemoryManager => _physicalMemoryManager; + public StatsCollector Stats { get; } public EvictionInfo LastEviction => _lastEviction; public void Reset() @@ -105,6 +107,7 @@ namespace VMdemo.Simulation _lastEviction = EvictionInfo.None; _hasPendingForcedVirtualAddress = false; _pendingForcedVirtualAddress = 0UL; + Stats.Reset(); _addressGenerator = new AddressGenerator(_config.vaBits, _seed); _tlbCache = new TlbCache(_config.tlbEntries); @@ -278,6 +281,17 @@ namespace VMdemo.Simulation private void ExecuteFinalize() { + State.CurrentCost = Stats.CalculateCost( + State.IsTlbHit, + State.IsPageTableHit, + State.IsPageFault, + _config.pageFaultPenalty); + Stats.RecordAccess( + State.IsTlbHit, + State.IsPageTableHit, + State.IsPageFault, + State.CurrentCost); + State.CurrentRound += 1; State.CurrentStep = TranslationStep.GenerateVA; } diff --git a/Assets/Tests/EditMode/Step6StatsCollectorTests.cs b/Assets/Tests/EditMode/Step6StatsCollectorTests.cs new file mode 100644 index 0000000..4b313b1 --- /dev/null +++ b/Assets/Tests/EditMode/Step6StatsCollectorTests.cs @@ -0,0 +1,135 @@ +using NUnit.Framework; +using VMdemo.Core; +using VMdemo.Simulation; + +namespace VMdemo.Tests.EditMode +{ + public class Step6StatsCollectorTests + { + [Test] + public void StatsCollector_Reset_ClearsAllCountersAndRates() + { + var stats = new StatsCollector(); + stats.RecordAccess(tlbHit: true, pageTableHit: false, pageFault: false, cost: 1); + stats.RecordAccess(tlbHit: false, pageTableHit: false, pageFault: true, cost: 104); + + stats.Reset(); + + Assert.That(stats.TotalAccess, Is.EqualTo(0)); + Assert.That(stats.TlbHit, Is.EqualTo(0)); + Assert.That(stats.PageTableHit, Is.EqualTo(0)); + Assert.That(stats.PageFault, Is.EqualTo(0)); + Assert.That(stats.TotalCost, Is.EqualTo(0L)); + Assert.That(stats.TlbHitRate, Is.EqualTo(0.0)); + Assert.That(stats.PageTableHitRate, Is.EqualTo(0.0)); + Assert.That(stats.AvgCost, Is.EqualTo(0.0)); + } + + [Test] + public void StatsCollector_CalculateCost_FollowsMvpCostModel() + { + var stats = new StatsCollector(); + + Assert.That(stats.CalculateCost(tlbHit: true, pageTableHit: false, pageFault: false, pageFaultPenalty: 100), Is.EqualTo(1)); + Assert.That(stats.CalculateCost(tlbHit: false, pageTableHit: true, pageFault: false, pageFaultPenalty: 100), Is.EqualTo(4)); + Assert.That(stats.CalculateCost(tlbHit: false, pageTableHit: false, pageFault: true, pageFaultPenalty: 100), Is.EqualTo(104)); + } + + [Test] + public void StatsCollector_RecordAccess_AggregatesCountersRatesAndAverage() + { + var stats = new StatsCollector(); + + stats.RecordAccess(tlbHit: true, pageTableHit: false, pageFault: false, cost: 1); + stats.RecordAccess(tlbHit: false, pageTableHit: true, pageFault: false, cost: 4); + stats.RecordAccess(tlbHit: false, pageTableHit: false, pageFault: true, cost: 104); + + Assert.That(stats.TotalAccess, Is.EqualTo(3)); + Assert.That(stats.TlbHit, Is.EqualTo(1)); + Assert.That(stats.PageTableHit, Is.EqualTo(1)); + Assert.That(stats.PageFault, Is.EqualTo(1)); + Assert.That(stats.TotalCost, Is.EqualTo(109L)); + Assert.That(stats.TlbHitRate, Is.EqualTo(1.0 / 3.0).Within(1e-12)); + Assert.That(stats.PageTableHitRate, Is.EqualTo(1.0 / 3.0).Within(1e-12)); + Assert.That(stats.AvgCost, Is.EqualTo(109.0 / 3.0).Within(1e-12)); + } + + [Test] + public void TranslatorEngine_StepOnce_ToFinalize_UpdatesStatsAndCurrentCost() + { + var engine = CreateEngine(pageFaultPenalty: 120, seed: 31); + engine.SetNextVirtualAddress(0x0000_1234UL); + + while (!engine.StepOnce()) + { + } + + Assert.That(engine.State.CurrentRound, Is.EqualTo(1)); + Assert.That(engine.State.IsPageFault, Is.True); + Assert.That(engine.State.CurrentCost, Is.EqualTo(124)); + Assert.That(engine.Stats.TotalAccess, Is.EqualTo(1)); + Assert.That(engine.Stats.PageFault, Is.EqualTo(1)); + Assert.That(engine.Stats.TotalCost, Is.EqualTo(124L)); + Assert.That(engine.Stats.AvgCost, Is.EqualTo(124.0)); + } + + [Test] + public void TranslatorEngine_RunOneAccess_TracksBatchSummaryWithMixedPaths() + { + var engine = CreateEngine(pageFaultPenalty: 100, seed: 41); + const ulong va = 0x0000_5678UL; + + var first = engine.RunOneAccess(va); // page fault + var second = engine.RunOneAccess(va); // tlb hit + + engine.TlbCache.Clear(); + var third = engine.RunOneAccess(va); // page table hit + + Assert.IsTrue(first.PageFault); + Assert.IsTrue(second.TlbHit); + Assert.IsTrue(third.PageTableHit); + + Assert.That(engine.Stats.TotalAccess, Is.EqualTo(3)); + Assert.That(engine.Stats.TlbHit, Is.EqualTo(1)); + Assert.That(engine.Stats.PageTableHit, Is.EqualTo(1)); + Assert.That(engine.Stats.PageFault, Is.EqualTo(1)); + Assert.That(engine.Stats.TotalCost, Is.EqualTo(109L)); + Assert.That(engine.Stats.TlbHitRate, Is.EqualTo(1.0 / 3.0).Within(1e-12)); + Assert.That(engine.Stats.PageTableHitRate, Is.EqualTo(1.0 / 3.0).Within(1e-12)); + Assert.That(engine.Stats.AvgCost, Is.EqualTo(109.0 / 3.0).Within(1e-12)); + } + + [Test] + public void TranslatorEngine_Reset_ClearsStats() + { + var engine = CreateEngine(pageFaultPenalty: 100, seed: 51); + engine.RunOneAccess(0x0000_9ABCUL); + Assert.That(engine.Stats.TotalAccess, Is.EqualTo(1)); + + engine.Reset(); + + Assert.That(engine.Stats.TotalAccess, Is.EqualTo(0)); + Assert.That(engine.Stats.TlbHit, Is.EqualTo(0)); + Assert.That(engine.Stats.PageTableHit, Is.EqualTo(0)); + Assert.That(engine.Stats.PageFault, Is.EqualTo(0)); + Assert.That(engine.Stats.TotalCost, Is.EqualTo(0L)); + Assert.That(engine.Stats.AvgCost, Is.EqualTo(0.0)); + Assert.That(engine.State.CurrentCost, Is.EqualTo(0)); + } + + private static TranslatorEngine CreateEngine(int pageFaultPenalty, int seed) + { + var config = new SimulationConfig + { + vaBits = 32, + pageSizeKB = 4, + physicalMemoryMB = 1, + tlbEntries = 4, + accessCount = 20, + pageFaultPenalty = pageFaultPenalty + }; + + return new TranslatorEngine(config, seed); + } + } +} diff --git a/Assets/Tests/EditMode/Step6StatsCollectorTests.cs.meta b/Assets/Tests/EditMode/Step6StatsCollectorTests.cs.meta new file mode 100644 index 0000000..45fba07 --- /dev/null +++ b/Assets/Tests/EditMode/Step6StatsCollectorTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 85f0d67cd32ddbe4ca06ce696b2426cf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/doc/MVP-TODO.md b/doc/MVP-TODO.md index ab0fa9d..69286f1 100644 --- a/doc/MVP-TODO.md +++ b/doc/MVP-TODO.md @@ -143,20 +143,20 @@ ### TODO -- [ ] 新建 `StatsCollector.cs` -- [ ] 累计:`totalAccess`、`tlbHit`、`pageTableHit`、`pageFault` -- [ ] 计算:`tlbHitRate`、`pageTableHitRate`、`avgCost` +- [x] 新建 `StatsCollector.cs` +- [x] 累计:`totalAccess`、`tlbHit`、`pageTableHit`、`pageFault` +- [x] 计算:`tlbHitRate`、`pageTableHitRate`、`avgCost` ### 开销模型(MVP) -- [ ] TLB 命中:`+1` -- [ ] TLB miss + 页表命中:`+4` -- [ ] 缺页:`+4 + pageFaultPenalty` +- [x] TLB 命中:`+1` +- [x] TLB miss + 页表命中:`+4` +- [x] 缺页:`+4 + pageFaultPenalty` ### 完成标准 -- [ ] 指标在单步和批量模式下都持续更新 -- [ ] 批量完成后给出最终汇总 +- [x] 指标在单步和批量模式下都持续更新 +- [x] 批量完成后给出最终汇总 ## 8. Step 7 - UI 绑定与流程动画