4. CXL.cache 事务分类:读、写、窥探,一个都不能少

聊到 CXL.cache 协议,我第一个想到的,就是它那套复杂又精妙的事务分类。说白了,你得先搞清楚设备到底在和主机“聊什么”,才能去跟踪它、优化它。我自己在刚接触 CXL 时,就吃过“混淆事务类型”的亏,调了两个星期的 bug,最后发现是写请求配了读属性。嗯,这种坑,咱们尽量别踩。

CXL.cache 的事务,按功能可以分为三大类:读事务、写事务和窥探事务。每一类下面还有子类型,我习惯把它们叫做“事务家族”。下面这张图,是我自己梳理的,你们先看一眼,有个整体印象。

CXL.cache 事务分类总览 CXL.cache 事务 读事务 写事务 窥探事务 RdCurr (读当前) RdOwn (读拥有) WrCurr (写当前) WrInit (写初始化) SnpCurr SnpOwn 说明 读/写事务由设备主动发起;窥探事务由主机发起,用于保持缓存一致性。 无数据返回(仅状态) 有数据负载 强制状态转换

4.1 读事务:设备想看看数据

读事务,是设备请求从主机内存中读取数据。你想想看,一个加速卡想要处理数据,总得先把数据从内存里拿过来吧?读事务就是干这个的。

读事务有两个变体:RdCurr 和 RdOwn。它们的区别,我总结在下表里了:

特性 RdCurr(读当前) RdOwn(读拥有)
语义 设备只读,不修改数据 设备后续可能会修改数据
返回缓存状态 S(共享)或 I(无效) E(独占)或 M(修改)
是否需要写回 否,设备不能修改 是,可能触发写回
典型用例 查表、配置空间读取 计算加载、原子操作准备

我记得有次调试一个 AI 推理卡,它的权重加载老是比预期慢。用协议分析仪抓了一圈,发现设备每次读取权重都用的是 RdOwn,导致主机要把缓存行独占给它。其实那些权重根本不会被修改,用 RdCurr 就够了。换成 RdCurr 之后,延迟降了 30%。

小经验: 如果设备只是读数据,并且之后不会写回去,优先选 RdCurr。别浪费缓存一致性协议的“独占”开销。

4.2 写事务:设备要往内存里写

写事务是设备发起写操作。但它不是简单地“把数据发过去就完事”,CXL.cache 的写事务还要和主机的缓存状态打交道。这里面有两个子类型:WrCurr 和 WrInit。

  • WrCurr(写当前):设备写数据,但不要求写回主存。主机可能只是把数据更新到自己的缓存里,并标记为脏。这适合写频繁、但不需要持久化的场景。
  • WrInit(写初始化):设备写数据,并且要求写操作“直达”主存,或者至少让主机感知到这是一个完整的写操作。常用于初始化一段内存。

你可能会问:“那我到底用哪种?”

我讲一个实际案例。有个做网络加速卡的团队,他们设备在处理数据包时,需要频繁更新统计计数器。他们用了 WrInit,结果每次写都要等主机确认,性能上不去。我建议他们改成 WrCurr,配合主机缓存策略,吞吐量直接翻倍。不过要注意,如果你的数据对持久性有要求,比如日志落盘,那别偷懒,该用 WrInit 还得用。

避坑提醒: 我曾经遇到过设备写 WrCurr 后立即读同一地址,结果读到旧数据。为什么?因为主机缓存还没处理完写请求。你想想看,这本质上是个“写-读冒险”。一定要在写完成后,再发读请求,或者用 Fence 机制保证顺序。

4.3 窥探事务:主机在想什么?

窥探事务(Snoop Transaction)是 CXL.cache 里比较“特殊”的一类。它不是设备发起的,而是主机主动发给设备的。目的是什么?为了保持缓存一致性。

想象一下:设备缓存了一行数据,结果主机那边另一个核心把这个数据改了。主机需要通知设备:“嘿,你那个数据脏了,别用了!”——这就是窥探。

常见的窥探事务有两种:

  • SnpCurr:主机询问设备当前有没有该数据。设备需要返回缓存状态,比如 S、E、M 或 I。
  • SnpOwn:主机要求设备交出数据的“所有权”。如果设备持有脏数据(状态 M),设备必须写回,然后转为 I 状态。

你想想看,如果设备不及时响应窥探,主机这边可能就拿到了错误的数据。所以,窥探响应的延迟,直接影响整个系统的性能。

核心要点: 在热点定位时,窥探事务延迟 是一个非常关键的指标。如果某条缓存行频繁被窥探,说明它在多个 Agent 之间“打架”,这就是热点。我在一个项目中,就是通过统计 SnpOwn 的频率,精准找到了一个被多设备争抢的锁变量。

4.4 怎么区分和记忆?

我习惯用一句话来记:

  • 读:设备要数据(付钱还是不付钱?—— RdCurr 不付,RdOwn 付)。
  • 写:设备给数据(直接放缓存还是非写到内存?—— WrCurr 放缓存,WrInit 写到底)。
  • 窥探:主机来问话(就问一问,还是逼你交东西?—— SnpCurr 问问,SnpOwn 交出来)。

当然,实际协议里还有一些组合事务和特殊标记位,比如数据是否有效、是否包含预取提示等。这些我们在后续章节会展开。但今天先把这三个大类记牢,后面的就好办多了。

4.5 实战建议:从分类开始定位热点

在《CXL.cache 事务跟踪》这门课里,你拿到一份 trace 日志,第一步不是看地址,而是先看事务类型分布。怎么分析?

  1. 统计各类事务的数量占比。如果写事务占比异常高,可能设备在频繁更新数据,或者缓存策略有问题。
  2. 检查窥探事务的分布。如果某一段地址空间被频繁 SnpOwn,那极有可能是热锁或伪共享。
  3. 对比 RdCurr 和 RdOwn 的比例。如果 RdOwn 占比过高,但设备实际很少写,那就是“过度申请所有权”,浪费带宽。
一个小工具: 我写过一个简单的脚本,能从 CXL trace 里提取事务类型,并画出饼图。你们可以试着用 Python 解析 CXL 日志,先按事务类型分组,再看看每个分组命中哪些地址。你会发现,热点往往藏在那些“独特性”的事务里。