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