From ccf95ec2700420eeb88d363197f7629a50d266e1 Mon Sep 17 00:00:00 2001 From: Youmu Liu <105042599+Nitload-NSI@users.noreply.github.com> Date: Fri, 13 Mar 2026 18:04:36 +0800 Subject: [PATCH] L4 brief ok --- .../HighPerformance/Memory/L4.Prefetch.cs | 4 ++ .../HighPerformance/Memory/L4_design.md | 56 +++++++++---------- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/StgSharp.Common/HighPerformance/Memory/L4.Prefetch.cs b/StgSharp.Common/HighPerformance/Memory/L4.Prefetch.cs index 8f51cb33..01da8ae0 100644 --- a/StgSharp.Common/HighPerformance/Memory/L4.Prefetch.cs +++ b/StgSharp.Common/HighPerformance/Memory/L4.Prefetch.cs @@ -63,6 +63,10 @@ namespace StgSharp.HighPerformance.Memory { ref CacheLinePrediction handle = ref span[i]; nuint address = handle.Address; + if (TestHit(address)) + { + continue; + } if (!TryGetEmptyLine(out int index)) { throw new OverflowException("L4 has no empty cache line."); } diff --git a/StgSharp.Common/HighPerformance/Memory/L4_design.md b/StgSharp.Common/HighPerformance/Memory/L4_design.md index 5d24baf9..7bbbae4e 100644 --- a/StgSharp.Common/HighPerformance/Memory/L4_design.md +++ b/StgSharp.Common/HighPerformance/Memory/L4_design.md @@ -187,7 +187,7 @@ RRPV数组使用byte存储每一个cacheline的使用情况,其低二位表示 | 时机 | 操作 | 说明 | |---------------|-----------------------------------|-----------------------------| | 加载时(插入) | RRPV = 0 | 刚加载的数据马上要用 | -| 命中时 | `_evictionFlags[i] &= 0xF9` | bit 1-2 清零,RRPV 归零 | +| 命中时 | `_evictionFlags[i] = 0` | bit 1-2 清零,RRPV 归零 | | 逐出扫描时 | 找 RRPV=3 的行逐出 | 找不到则全体 RRPV+1(老化) | **相比 v0.1 的 4 级环形链表的优势**: @@ -207,39 +207,31 @@ RRPV数组使用byte存储每一个cacheline的使用情况,其低二位表示 **职责**:为可写缓存行提供灵活的写回策略,通过本机函数指针避免托管开销。 **设计原则**:所有可写缓存行默认视为脏数据。逐出或显式刷新时, -L4 通过注册的函数指针调用写回逻辑,调用者决定如何预处理和写回。 +L4 本身不负责管理写回策略,写回策略由绑定的预测器负责实现。 + +预测器通过如下的接口进行定义 ``` -写回流程: + public interface IPrefetchPredict + { - 逐出脏缓存行 slot[i]: - 1. 检查 dirty 位:(_evictionFlags[i] & 0x01) != 0 - 2. 调用预处理:_preprocess[i](cacheline_addr, _callbackCtx[i]) - → 例如:将 Pack 格式反向重排为原始矩阵布局 - 3. 调用写回: _writeback[i](origin_addr, cacheline_addr, _callbackCtx[i]) - → 例如:累加 partial sum 到目标矩阵 - 4. 标准逐出:注销映射,归还 Slab -``` + int Predict( + Span node + ); -**回调签名**(本机函数指针): + bool Prefetch( + nuint origin, + ulong policy, + nuint cache + ); -```csharp -/// -/// 写回预处理回调。在写回前对缓存行数据进行变换。 -/// 例如:将 Pack 重排的数据恢复为原始布局。 -/// -/// 缓存行本地地址 -/// 调用者提供的上下文 -delegate* unmanaged Preprocess; + void WriteBack( + nuint origin, + ulong policy, + nuint cache + ); -/// -/// 写回回调。将缓存行数据写回到目标地址。 -/// 例如:将 partial sum 累加到目标矩阵。 -/// -/// 原始目标地址 -/// 缓存行本地地址 -/// 调用者提供的上下文 -delegate* unmanaged Writeback; + } ``` **典型回调实现**: @@ -396,7 +388,7 @@ private void TryRefreshPrefetch() ### 4.3 缓存未命中(只读) 1. Swiss Table 查找未命中 -2. 计算线程进入 SpinWait 等待循环 +2. 计算线程进入 Thread.Sleep(0) 等待循环 3. 等待 Predictor 驱动的预取刷新将该行加载进 L4 4. Swiss Table 出现命中后退出等待 5. 构造 CacheLine 句柄并返回 @@ -653,6 +645,7 @@ L4 的 Predictor 是**确定性的 Oracle**——它不预测,它知道。 ### 6.3 回调驱动的写回策略 通过本机函数指针实现写回,避免了: + - 虚方法调用开销 - 接口分发开销 - 委托的 GC 压力 @@ -671,6 +664,7 @@ L4 在逐出或 Flush 时直接通过函数指针调用, ### 6.5 缓存行大小与矩阵 Kernel 对齐 8192B 的缓存行大小不是任意选取的: + - 1 个 `double MAT_KERNEL`(4×4 double 矩阵)= 128 Bytes - 1 个缓存行 = 64 个 `MAT_KERNEL` - 矩阵乘法的微内核每次处理的数据量与缓存行完全对齐 @@ -719,7 +713,7 @@ L4 在逐出或 Flush 时直接通过函数指针调用, |-----------------|-------------------------------|---------------------------------------| | 逐出数据结构 | 4 级环形双向链表 | RRPV 2-bit 掩码 + 8KB SoA 数组 | | 逐出扫描方式 | 链表头指针遍历 | SSE2 批量模式匹配(16 行/周期) | -| 热���更新 | 链表节点摘插(4 指针写) | 单字节位运算 `&= 0xF9` | +| 热���更新 | 链表节点摘插(4 指针写) | 单字节位运算 `&= 0x03` | | 老化操作 | 逐节点移链表 | SIMD 批量 +1 | | 引用保护 | 无 | refCount > 0 时 SIMD 扫描跳过 | | 预取策略 | 无 | Predictor 窗口 OPT(2048 条缓冲) | @@ -758,4 +752,4 @@ L4 在逐出或 Flush 时直接通过函数指针调用, - StgSharp SIMD Architecture (`SIMDID` 体系) - StgSharp NRGA Roadmap - StgSharp `SlabAllocator` 实现 -- StgSharp `SwissTable` 实现及 Benchmark \ No newline at end of file +- StgSharp `SwissTable` 实现及 Benchmark