第一章 CXL.cache 概述
各位好,我是这次课的主讲。先聊聊 CXL.cache,说白了就是 Compute Express Link 协议里专门管缓存一致性的那一套东西。很多做 PCIe 的老朋友一看就觉得像 PCIe,但差别大了去了——CXL.cache 走的是缓存一致性协议,而不是简单的内存读写。我记得几年前第一次调 CXL.cache 的 RTL 仿真,就被 snoop 延迟搞得头疼,后来才明白问题出在哪。
CXL 协议简介
CXL 协议分三个子协议:CXL.io、CXL.cache、CXL.mem。今天重点讲 CXL.cache。它允许一个设备(比如加速器、智能网卡)直接访问主机 CPU 的缓存,并且保证数据一致性。你想想看,如果没有一致性,设备读到的可能是过时的数据——那后果就严重了。
CXL.cache 本质上是一个“设备发起请求、主机响应”的 master-slave 模型。设备作为 master,主机作为 slave。设备可以发 read request、write request,还可以主动发 snoop(监听)请求。嗯,这里要注意,设备跟主机地位不是对等的。
Cache 一致性基本模型
多 Socket 环境下,每个 CPU 有自己的 Cache,还有共享的 memory。CXL.cache 在这基础上又加了一层——设备侧的 Cache。于是问题来了:设备 Cache 里的数据跟 CPU Cache 里的数据,谁才是最新版本?
CXL.cache 用的是 MESI 协议的变体,叫 MESIF 或者 MOESI,具体看实现。我接手过一个项目,设备本地有个 16MB 的 SRAM 作为 Cache,每次 DMA 前都要做一遍“无效化”操作,否则 CPU 读到脏数据。
| 状态 | 含义 | CXL.cache 里的典型场景 |
|---|---|---|
| Modified (M) | 数据已修改,独占 | 设备写了本地 Cache,还没写回内存 |
| Exclusive (E) | 数据有效且未被其他修改 | 设备刚读回数据,还没动过 |
| Shared (S) | 数据在多个 agent 中都干净 | CPU 和设备同时持有同一份 |
| Invalid (I) | 数据无效 | 被其他 agent 用 snoop 抢占后 |
| Forward (F) | 转发给请求者,自己仍保留 | 多 Socket 专用,减少跨 Socket 流量 |
我自己写 FPGA 加速器时就踩过坑——设备侧 Cache 的状态机没处理好 M→I 的过渡,导致 CPU 读不到最新的计算结果。后来加了一条 snoop forward 路径才解决。
多 Socket 环境的引入与挑战
单 Socket 下 CXL.cache 相对好办——所有请求走同一根 coherence bus,延迟可预测。但多 Socket 一上来,问题就成几何级数增长:
- 跨 Socket 延迟:CXL.cache 请求可能需要穿过 UPIC/QPI/Infinity Fabric 到远端 Socket,延迟比本地高 2~3 倍。
- Snoop 风暴:一个设备发 snoop 请求,可能广播到所有 Socket 的所有核心,引发总线拥塞。
- 目录协议 vs 广播协议:多 Socket 通常用 directory-based 协议,但 CXL.cache 设备不知情,容易频繁走广播,浪费带宽。
- NUMA 亲和性:你的设备插在哪个 Socket 上?内存分配是否靠近设备?不注意这些,性能掉一半。
我负责过一个 8-Socket 超融合系统,里面加了 16 张 CXL.cache 加速卡。最初我们把所有激活的设备都配置成“总是从 Node0 的 home agent 拿目录信息”,结果跨 Socket 拥塞把网络都打满了。后来改成按 NUMA 节点分配 home agent,延迟降了 40%。
本章知识点结构图
下面这张图概括了 CXL.cache 在多 Socket 环境中的位置和组件关系:
关键认识:CXL.cache 在多 Socket 下的性能瓶颈不是 Cache 本身,而是一致性协议的传播损耗。Snoop Filter 和 Home Agent 的布局直接影响延迟。我建议在设计初期就用性能模型扫一遍跨 Socket 通讯热点,别等到板上测出来才追。
好了,这一章就这些。核心概念都在这了:CXL 协议位置、MESI 变体、多 Socket 挑战。后面我们会深入每个瓶颈的具体优化手段——比如怎么配 snoop filter、怎么避免目录冲突、怎么绑定 NUMA 节点。