L4 brief ok

This commit is contained in:
Youmu Liu
2026-03-13 18:04:36 +08:00
parent 5da448548b
commit ccf95ec270
2 changed files with 29 additions and 31 deletions
@@ -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.");
}
@@ -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<CacheLinePrediction> node
);
**回调签名**(本机函数指针):
bool Prefetch(
nuint origin,
ulong policy,
nuint cache
);
```csharp
/// <summary>
/// 写回预处理回调。在写回前对缓存行数据进行变换。
/// 例如:将 Pack 重排的数据恢复为原始布局。
/// </summary>
/// <param name="cacheLineAddr">缓存行本地地址</param>
/// <param name="context">调用者提供的上下文</param>
delegate* unmanaged<nuint, nuint, void> Preprocess;
void WriteBack(
nuint origin,
ulong policy,
nuint cache
);
/// <summary>
/// 写回回调。将缓存行数据写回到目标地址。
/// 例如:将 partial sum 累加到目标矩阵。
/// </summary>
/// <param name="originAddr">原始目标地址</param>
/// <param name="cacheLineAddr">缓存行本地地址</param>
/// <param name="context">调用者提供的上下文</param>
delegate* unmanaged<nuint, nuint, nuint, void> 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<T>` 实现
- StgSharp `SwissTable` 实现及 Benchmark
- StgSharp `SwissTable` 实现及 Benchmark