上一节把计算单元排了兵布了阵,本节回答它们最关心的问题:数据住在哪。存储层次是三大件里数字最多的一层——每级的容量、延迟、带宽都是要在架构阶段写死的参数。读完本节,你应当能画出一颗 SoC 的存储金字塔,给每层填上说得出口的数字,并且能裁断工程里最常吵架的问题:这块缓冲区该放在哪、归谁管。
存储器有个绕不开的物理事实:越快,越贵,越难做大。寄存器一个比特的代价能买片外存储几千上百万个比特;反过来,大容量存储的速度又慢出几个数量级。既然两头都不肯让步,工程就发明了折中术——把数据按"被用到的可能性"分层摆放:越可能马上被用到的,放得离计算单元越近。程序访问数据有很强的局部性:刚用过的数据大概率马上再用,用过它旁边的数据大概率马上用旁边。 locality 在,金字塔就成立:绝大多数请求被顶层拦下,少数穿透到底层,整体表现得又快又大。
CK770 的存储层次从上到下六层,先把每层的量级和角色钉死。
| 层级 | 典型容量(CK770) | 访问量级 | 归属与角色 |
|---|---|---|---|
| 处理器寄存器 | 每核几十个 | 零周期 | 指令的操作数暂存,编译器分配 |
| 一级缓存 | 每核几十 KB | 一到几个周期 | 私有,拆分指令与数据两块 |
| 二级缓存 | 每簇几百 KB | 十来个周期 | 簇内共享,一致性的中枢 |
| 片上紧耦合存储 | 数百 KB | 十来个周期 | 物理地址直挂,无一致性逻辑 |
| 片外存储控制器 | 外接 GB 级 | 几十到上百周期 | DDR 控制器统一调度 |
| 外部存储介质 | 更大 | 更慢 | 固件与文件系统,不属于运行层次 |
表里有个 newcomer 值得单独说:片上紧耦合存储。它长得像缓存,本质却是普通物理地址的静态存储器——没有命中判断、没有一致性协议、软件说了算。代价是软件要自己管放什么进去;好处是行为完全可预测,访问延迟是常数。实时任务(中断处理、采集循环)把工作数据钉在紧耦合存储里,就躲开了缓存未命中的不确定性。CK770 给小核簇配了一块紧耦合存储,专门安置采集循环的热数据,这个决定在第五章的实时性排错里立了大功。

架构评审常出现这样的对话:"缓存多大合适?""越大越好。""多大是够?"然后冷场。够不够有个可操作的定义:工作集能装下,就是够。工作集指程序在一个调度周期内活跃读写的数据总量。估法是拿负载分类逐项列:采集循环的输入缓冲加输出缓冲、协议栈的套接字缓冲、推理模型的权重切片,按簇汇总,再乘一点二到一点五的安全系数。
CK770 的实际估算走一遍:采集循环的原始帧缓冲与处理缓冲合计百余 KB,钉在小核簇的紧耦合存储;协议栈套接字缓冲几十 KB 加推理权重切片,落在大核二级缓存;采集到推理的交接缓冲因为要跨簇交换,放片外存储,由 2.3 节的带宽账本管。三层答案对应三种归属,这就是容量估算的完整输出——不是一个数字,是一张带归属的清单。
归属是比容量更重要的输出。缓冲区有三个属性必须写进架构文档:谁分配(决定它落在哪层)、谁独占(决定要不要一致性)、谁回收(决定生命周期边界)。上一节的加速器三件套里,描述符指向的缓冲区就要写明"加速器独占写、CPU 只读交接区",归属不清的缓冲区是多核系统最难查的一类故障——第五章的 CDC 排错案例里会见到它的变体。
数据终究要落到片外大容量存储,从这层往上的每一级都只是它的缓存。片外存储控制器因此是全芯片带宽的总闸,它做三件事:把各主设备的读写请求排队调度、维护动态存储介质的刷新与预充电节拍、决定突发传输怎么切分。控制器调度策略对性能的影响不亚于互连——同样的请求流,好的调度能省下可观的等待时间。
架构阶段对控制器要做两个决定。一是位宽与速率,从带宽预算倒推:预算表(2.3 节)算出峰值带宽需求,加上三成余量,对照介质规格选型。二是服务质量配置:给实时域(采集、显示这类不能等的)留保障带宽,给尽力而为域(推理批处理这类慢点无妨的)吃剩余。CK770 就在这里栽过跟头——2.3 节的超支案例里,正是"推理批处理把实时采集的保障带宽挤掉"引发了第一次架构级返工,这里先按下不表。
容量之外还有一本带宽账:金字塔自上而下,各层的带宽也应逐级放大——底层若比上层还窄,未命中的代价就不再是延迟,而是整个通路的拥堵。核算方法与 2.3 节的预算表合流:每层的进出租户流量对齐该层的供给能力,任何一层"进水快于出水",堵的就是那一层。容量与带宽两本账对照着记,存储层次才算真正设计过,而不是照抄教科书画了个金字塔。
缓存行是缓存与下层交换数据的最小单位,常见几十个字节。层次之间搬运不以变量为单位、而以缓存行为单位,这个粒度决定了一堆现象:相邻变量会被顺手一起装进缓存(空间局部性的物理载体);一个变量横跨两行会带来两次搬运;上一节说的假共享,本质是两个无关变量共享了同一行,作废粒度被迫扩大。软件侧的对策因此都很朴素——热数据按缓存行对齐、频繁写的量各自成行、跨核交接的结构体按行边界补齐。
分写穿与写回两种策略。写穿是写操作同时更新缓存与下层,简单但占用带宽;写回是只改缓存、给该行记一个"脏"标记,等行被逐出时才落盘——带宽省了,代价是多一层不一致的窗口与更复杂的逐出逻辑。软件需要关心它,主要在两个场合:多核共享数据时要靠显式的同步指令强制可见,不能指望"写了就该看得见";对持久性有要求的场景(掉电保数据),要显式把脏数据刷到真正非易失的介质,缓存与片外存储控制器里的排队写都不算数。
能,而且这正是 CK770 的用法。物理地址空间划分两条路:实时数据段直挂紧耦合存储,访问常数延迟、不参与一致性;其余地址走缓存层次。代价是软件要维护这张"什么放哪"的地图——链接脚本与运行时分配都要遵守。收益在第五章那个实时性案例里兑现:采集循环的数据从不受缓存未命中抖动,延迟可预测。判断准则依然是可预测性值不值软件管理成本,实时性要求越高答案越倾向值。
物理上做不到,因为层次不是设计惰性的产物,而是距离与引脚的物理妥协。片外存储与处理器隔着封装、走线与连接器,信号要驱动大电容负载,速度上限比片内低一两个数量级;引脚数也封死了带宽天花板——3.2 节的串行化能把速率抬上去,但抬不掉访问延迟里的往返路程。缓存层次的全部意义,是把"绝大多数访问"留在近处完成,只让"必须出远门"的少数请求承担路途代价。层次会随工艺演化增减层数,但"快而小在近处、慢而大在远处"的骨架不会翻篇。
数据住址定好了,下一节看它们怎么在路上跑:互连、仲裁与那次著名的带宽超支。