Files
stgsharp/test/StgSharpDebug/TestL4Behaviour.cs.old
T

435 lines
17 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="TestL4Behaviour"
// Project: StgSharp
// AuthorGroup: Nitload
// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using Nitload.Common.HighPerformance.Memory;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Nitload
{
// ====================================================================
// TrackingPredictor
//
// 每次 Predict 返回全部地址(幂等);
// Prefetch 用可配置字节填充缓存行;
// WriteBack 记录被回写的 origin,供驱逐测试断言。
// ====================================================================
internal sealed unsafe class TrackingPredictor : IL4Predict
{
private readonly nuint[] _addresses;
public byte FillByte = 0xAB;
public readonly List<nuint> WriteBackLog = [];
public TrackingPredictor(
nuint[] addresses
)
{
_addresses = addresses;
}
public bool Predict(
Span<CacheLineDescription> node,
out int count
)
{
count = Math.Min(_addresses.Length, node.Length);
for (int i = 0; i < count; i++) {
node[i] = new CacheLineDescription
{
Address = _addresses[i],
MapPolicy = 0,
};
}
return count > 0;
}
public void Prefetch(
nuint origin,
ulong policy,
Span<byte> cache
)
{
cache.Fill(FillByte);
}
public void WriteBack(
nuint origin,
ulong policy,
ReadOnlySpan<byte> cache
)
{
WriteBackLog.Add(origin);
}
}
// ====================================================================
// P1 行为测试
// ====================================================================
internal static partial class Program
{
// ----------------------------------------------------------------
// 入口
// ----------------------------------------------------------------
public static void TestL4Behavior()
{
_l4FailCount = 0;
Console.WriteLine("=== L4 Behavior Tests (P1) ===");
Console.WriteLine();
L4Test_MultipleRef_SameLine();
L4Test_AgeAll_SaturatesAt3();
L4Test_Evict_RemovesStaleLines();
Console.WriteLine();
if (_l4FailCount == 0)
{
Console.WriteLine("=== ALL P1 TESTS PASSED ===");
} else
{
Console.WriteLine($"=== {_l4FailCount} TEST(S) FAILED ===");
}
}
// ----------------------------------------------------------------
// 辅助:读取 CacheLine 第一个字节
// ----------------------------------------------------------------
private static unsafe byte L4ReadFirstByte(
L4.CacheLine line
)
{
return *(byte*)line.Address;
}
// ================================================================
// P1-2 AgeAll_SaturatesAt3
//
// 目标:连续触发多次 AgeAll 后,所有已分配行的 RRPV 字节
// 均饱和在 3,不溢出、不归零。
//
// 策略:
// 1. 预取 K 条行并立即 Dispose(refCount=0,RRPV 初始为 0)
// 2. 调用 ForceAgeAll() 4 次(通过新增的 Debug 接口直接触发)
// 3. 读取 DebugSnapshot.RrpvSnapshot,断言所有已分配行 == 3
//
// 注意:AgeAll 是 private,测试依赖下方"在 L4.Debug.cs 追加
// public void DebugAgeAll()" 的接口,见文末说明。
// ================================================================
private static void L4Test_AgeAll_SaturatesAt3()
{
Console.Write("[P1-2] AgeAll_SaturatesAt3 ... ");
try
{
const int K = 4;
nuint[] addresses = MakeL4FakeAddresses(K);
using L4 l4 = new L4();
l4.PrefetchPredict = new TrackingPredictor(addresses);
// Step 1: 装填 K 条行,全部 Dispose(refCount=0)
for (int i = 0; i < K; i++)
{
L4.CacheLine line = l4.Map(addresses[i]);
line.Dispose();
}
// 装填后 RRPV 应全为 0(Map 命中时 eviction[id] = 0)
{
L4.DebugSnapshot snap = l4.GetDebugSnapshot();
for (int i = 0; i < snap.MaxId; i++) {
L4Assert(
snap.RrpvSnapshot[i] == 0,
$"{$"RrpvSnapshot[{i}] should be 0 right after Map, "}{$"got {snap.RrpvSnapshot[i]}"}");
}
}
// Step 2: AgeAll 4 次(饱和值为 3,4 次足够触顶)
for (int age = 0; age < 4; age++)
{
l4.DebugAgeAll();
}
// Step 3: 所有已分配行 RRPV == 3
{
L4.DebugSnapshot snap = l4.GetDebugSnapshot();
L4Assert(
snap.MaxId == K,
$"MaxId should be {K}, got {snap.MaxId}");
bool allSaturated = true;
for (int i = 0; i < snap.MaxId; i++)
{
if (snap.RrpvSnapshot[i] != 3)
{
L4Assert(false,
$"{$"RrpvSnapshot[{i}] == {snap.RrpvSnapshot[i]}, "}expected 3 after 4x AgeAll (saturation failure or overflow)");
allSaturated = false;
}
}
if (allSaturated) {
Console.WriteLine($"PASS (MaxId={snap.MaxId}, all RRPV==3)");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"FAIL ({ex.GetType().Name}: {ex.Message})");
_l4FailCount++;
}
}
// ================================================================
// P1-3 Evict_RemovesStaleLines
//
// 目标:RRPV==3 且 refCount==0 的行在 Evict 后:
// a. WriteBack 被调用,origin 是 batch1 中的地址
// b. DebugContains(evictedOrigin) == false(SwissTable 条目已移除)
// c. 该地址重新 Map 后,缓存行被重新 Prefetch(填充字节变为新值)
//
// 策略:
// 1. batch1(K 条) 装填 → Dispose → DebugAgeAll × 4(RRPV 饱和到 3)
// 2. 换 Predictor(FillByte 改为 0xCC),地址列表扩充为 batch1 + 1 条新地址
// 3. Map 新地址 → 触发 Prefetch → Prefetch 内部 Evict → WriteBack
// 4. 三项断言
// ================================================================
private static void L4Test_Evict_RemovesStaleLines()
{
Console.Write("[P1-3] Evict_RemovesStaleLines ... ");
try
{
const int K = 4;
nuint[] batch1 = MakeL4FakeAddresses(K);
TrackingPredictor predictor1 = new TrackingPredictor(batch1)
{
FillByte = 0xAA
};
using L4 l4 = new L4();
l4.PrefetchPredict = predictor1;
// ---------- Phase 1: 装填 batch1,全部 Dispose(refCount=0)----------
for (int i = 0; i < K; i++)
{
L4.CacheLine line = l4.Map(batch1[i]);
L4Assert(
L4ReadFirstByte(line) == 0xAA,
$"Initial fill should be 0xAA, got 0x{L4ReadFirstByte(line):X2}");
line.Dispose();
}
// 确认 K 条行均在 SwissTable 中
for (int i = 0; i < K; i++)
{
L4Assert(
l4.DebugContains(batch1[i]),
$"batch1[{i}] should be in SwissTable after prefetch");
}
// ---------- Phase 2: AgeAll × 4,使 RRPV 饱和到 3 ----------
for (int age = 0; age < 4; age++)
{
l4.DebugAgeAll();
}
L4.DebugSnapshot snap = l4.GetDebugSnapshot();
for (int i = 0; i < snap.MaxId; i++) {
L4Assert(
snap.RrpvSnapshot[i] == 3,
$"Pre-evict: RrpvSnapshot[{i}] should be 3, got {snap.RrpvSnapshot[i]}");
}
// ---------- Phase 3: 直接调用 DebugEvict(1),驱逐 1 条 ----------
// 此时 refCount==0 且 RRPV==3,Evict 应能找到候选
TrackingPredictor predictor2 = new TrackingPredictor(batch1)
{
FillByte = 0xCC
};
l4.PrefetchPredict = predictor2; // 换 predictor 以捕获 WriteBack 日志
int remain = l4.DebugEvict(1);
// 断言 a: remain==0,说明成功驱逐了 1 条
L4Assert(
remain == 0,
$"DebugEvict(1) should return 0 (all evicted), got remain={remain}");
// 断言 b: WriteBack 被调用,origin 属于 batch1
L4Assert(
predictor2.WriteBackLog.Count > 0,
"Evict should have called WriteBack for the evicted line");
nuint evictedOrigin = nuint.Zero;
foreach (nuint logged in predictor2.WriteBackLog)
{
foreach (nuint b1 in batch1)
{
if (logged == b1)
{
evictedOrigin = logged;
break;
}
}
if (evictedOrigin != nuint.Zero)
{
break;
}
}
L4Assert(
evictedOrigin != nuint.Zero,
$"{$"WriteBack origin should be a batch1 address, "}{$"WriteBackLog=[{string.Join(", ", predictor2.WriteBackLog)}]"}");
// 断言 c: SwissTable 中已不含该 origin
L4Assert(
!l4.DebugContains(evictedOrigin),
$"DebugContains(0x{evictedOrigin:X}) should be false after eviction");
// 断言 d: 被驱逐的地址重新 Map → 重新 Prefetch → 填充 0xCC(非旧值 0xAA)
// 注意:此时需要 predictor2 的 Predict 也包含 evictedOrigin,
// 使 Map 触发 Prefetch 时能重新装填该行
L4.CacheLine remapped = l4.Map(evictedOrigin);
byte refill = L4ReadFirstByte(remapped);
remapped.Dispose();
L4Assert(
refill == 0xCC,
$"{$"Re-mapped evicted line should be re-prefetched with 0xCC, "}{$"got 0x{refill:X2} (0xAA = stale data not evicted)"}");
if (_l4FailCount == 0) {
Console.WriteLine(
$"{$"PASS (evicted 0x{evictedOrigin:X}, remain={remain}, "}{$"WriteBack×{predictor2.WriteBackLog.Count})"}");
}
}
catch (Exception ex)
{
Console.WriteLine($"FAIL ({ex.GetType().Name}: {ex.Message})");
_l4FailCount++;
}
}
// ================================================================
// P1-1 MultipleRef_SameLine
//
// 同一 origin 同时持有 N 个句柄时:
// a. 所有句柄 Address 非零
// b. 所有句柄 Address 相同(同一缓存行)
// c. 全部 Dispose 后 ActiveCount 归零
// d. 再次 Map 同一地址仍然成功(refCount 归零不破坏可访问性)
// ================================================================
private static void L4Test_MultipleRef_SameLine()
{
Console.Write("[P1-1] MultipleRef_SameLine ... ");
const int N = 8;
try
{
nuint[] addresses = MakeL4FakeAddresses(1);
using L4 l4 = new L4();
l4.PrefetchPredict = new TrackingPredictor(addresses);
nuint target = addresses[0];
// ref struct 不能形成数组,展开为 N 个具名局部变量同时持有
L4.CacheLine l0 = l4.Map(target);
L4.CacheLine l1 = l4.Map(target);
L4.CacheLine l2 = l4.Map(target);
L4.CacheLine l3 = l4.Map(target);
L4.CacheLine l4b = l4.Map(target);
L4.CacheLine l5 = l4.Map(target);
L4.CacheLine l6 = l4.Map(target);
L4.CacheLine l7 = l4.Map(target);
// a. 所有句柄 Address 非零
L4Assert(l0.Address != nuint.Zero, "l0.Address should not be zero");
L4Assert(l1.Address != nuint.Zero, "l1.Address should not be zero");
L4Assert(l2.Address != nuint.Zero, "l2.Address should not be zero");
L4Assert(l3.Address != nuint.Zero, "l3.Address should not be zero");
L4Assert(l4b.Address != nuint.Zero, "l4b.Address should not be zero");
L4Assert(l5.Address != nuint.Zero, "l5.Address should not be zero");
L4Assert(l6.Address != nuint.Zero, "l6.Address should not be zero");
L4Assert(l7.Address != nuint.Zero, "l7.Address should not be zero");
// b. 所有句柄 Address 相同(同一缓存行)
nuint first = l0.Address;
L4Assert(l1.Address == first, $"l1 0x{l1.Address:X} != l0 0x{first:X}");
L4Assert(l2.Address == first, $"l2 0x{l2.Address:X} != l0 0x{first:X}");
L4Assert(l3.Address == first, $"l3 0x{l3.Address:X} != l0 0x{first:X}");
L4Assert(l4b.Address == first, $"l4b 0x{l4b.Address:X} != l0 0x{first:X}");
L4Assert(l5.Address == first, $"l5 0x{l5.Address:X} != l0 0x{first:X}");
L4Assert(l6.Address == first, $"l6 0x{l6.Address:X} != l0 0x{first:X}");
L4Assert(l7.Address == first, $"l7 0x{l7.Address:X} != l0 0x{first:X}");
// c. 持有期间 ActiveCount == N
{
L4.DebugSnapshot snap = l4.GetDebugSnapshot();
L4Assert(
snap.ActiveCount == N,
$"ActiveCount should be {N} while {N} handles live, got {snap.ActiveCount}");
}
// 全部 Dispose
l0.Dispose();
l1.Dispose();
l2.Dispose();
l3.Dispose();
l4b.Dispose();
l5.Dispose();
l6.Dispose();
l7.Dispose();
// c(续). Dispose 后 ActiveCount 归零
{
L4.DebugSnapshot snap = l4.GetDebugSnapshot();
L4Assert(
snap.ActiveCount == 0,
$"ActiveCount should be 0 after all disposed, got {snap.ActiveCount}");
}
// d. 再次 Map 仍然成功
L4.CacheLine again = l4.Map(target);
L4Assert(again.Address != nuint.Zero,
"Map after all refs disposed should still return valid address");
again.Dispose();
Console.WriteLine("PASS");
}
catch (Exception ex)
{
Console.WriteLine($"FAIL ({ex.GetType().Name}: {ex.Message})");
_l4FailCount++;
}
}
}
}