H6 将计算机分为 CPU、内存与外设,H9 介绍了 CPU 执行指令的基本过程。本课沿着数据到达 CPU 的路径继续向外观察,从寄存器、各级 Cache,到主存、磁盘和网络上的存储服务。学习目标是用局部性解释缓存为何有效,用延迟量级识别主要等待,并把工作集与延迟预算写入方案。这里先建立共同框架,不展开组相联结构与缓存一致性协议。
为什么需要存储层次
理想中的存储器应当容量很大、读取很快、价格很低,但现实器件与系统难以同时满足这些要求。贴近计算单元的高速存储占用宝贵的芯片面积,单位容量成本高;能以较低成本保存大量数据的设备,访问路径又更长。系统因此把不同特性的存储组织成层次,让少量近期可能使用的数据靠近 CPU,让更大量的数据保存在较远处。
寄存器直接服务于正在执行的指令,容量很小。Cache 是高速缓存,通常由硬件自动保留主存中部分数据的副本;L1、L2、L3 分别表示不同层级的 Cache,具体容量、共享范围与结构随处理器而异。主存通常指运行程序使用的 DRAM,容量比片上缓存大得多。SSD、HDD 等设备主要承担持久保存任务,访问方式也与普通主存不同。
层次的意义并非要求所有数据逐级搬上来后才可以使用,而是让常用数据有机会停留在近处。以程序读取数组为例,若所需内容已经进入 Cache,后续访问可以很快完成;若不在,就需要向更远处获取。多数普通程序无需为每次读取指定使用哪级 CPU Cache,但数据布局与访问顺序会影响硬件能否有效保留和复用内容。
容量、速度、成本之间的关系是 1 种设计约束,不是对所有产品的绝对排序。不同代际设备可能交叉,同样容量也可能采用不同技术。金字塔表达的是常见系统中的总体方向:越靠近计算,容量通常越小、单位容量成本越高、访问延迟通常越低。采购时仍须比较具体规格与负载,不能只按层级名称判断性能。

还应区分“存得住”和“拿得快”。某服务的全部业务数据可能有数 TB,无法全部放进昂贵的高速层,但某个时间窗口内反复访问的内容可能只有数 GB。只要这部分内容能留在合适的位置,系统就可以兼顾容量与速度。这个在特定时间窗口内实际活跃的数据集合,称为工作集;讨论工作集时必须同时给出时间窗口和访问范围。
层次之间还存在不同的管理者。CPU Cache 多由硬件管理,操作系统可能用空闲内存缓存文件内容,应用又可能维护自己的对象缓存。它们缓存的对象、淘汰方式与可观测指标不同。“缓存命中率很高”如果没有指出是哪层,就不足以解释端到端性能。先画出实际访问路径,才能把等待放到正确的位置。
局部性让少量高速空间发挥作用
时间局部性指刚使用过的内容在不久之后可能再次使用。循环中的计数变量、反复查询的热点索引、短时间内多次读取的配置,都可能具有时间局部性。缓存保留这些内容,可以减少重复向远处获取的次数。不过,“曾经读过”不等于“永远保留”,其他访问会竞争有限的空间,间隔足够长也可能失去复用机会。
空间局部性指访问某个位置之后,接下来可能访问附近的位置。顺序遍历连续数组就是典型例子:读取当前元素时带入的附近数据,很可能马上被使用。这里的“附近”主要指地址相邻,并非业务含义接近。链表中的相邻节点在逻辑上相邻,却可能分散在地址空间中,因此不一定具有数组那样的空间局部性。
可以把访问序列看作带有地址的时间线。序列 A、B、A、B 中,后面的 A 与 B 复用了先前内容,体现时间局部性;序列 A、A+1、A+2、A+3 中,访问沿着连续位置前进,体现空间局部性。实际程序常常兼有两者,例如反复扫描某段小数组,既利用相邻元素,又在下一轮复用整个数组。

这个交互用简化的缓存展示访问轨迹:第 1 次访问需要取入数据,重复访问可能命中,同一行内的邻近位置也可能随之受益。图中的行大小与缓存容量只为帮助观察,不代表所有处理器的实际配置。观察时应把“访问元素”与“取入一行”分开,元素可以很小,硬件传输的单位却可能更大。
局部性不是程序标签,而是具体访问方式的性质。同一份数据,按行扫描与跨很大步长跳读,可能表现不同;同一查询,在热点集中与请求均匀分散时,也可能表现不同。因此不能仅凭“用了数组”或“有缓存”就断定访问很快。需要结合元素布局、访问间隔、每轮覆盖范围以及同时运行的其他任务判断。
工作集还可能随业务阶段变化。启动时读取许多配置,运行稳定后反复处理少量热点,批处理时又扫描大范围历史数据,这些阶段对应不同的活跃集合。固定容量的缓存对某个阶段有效,对其他阶段可能帮助有限。性能报告应说明测量阶段,否则把预热后的结果用于冷启动,就会低估第 1 次访问的等待。
局部性也解释了为什么“多取一些”有时合算。若取来的相邻内容马上被使用,额外传输换来了后续访问的减少;若程序只碰到其中很少一部分,就可能浪费带宽与缓存空间。优化的方向是提高被取入数据的有效使用比例,而不是不加区分地扩大每次取数的范围。
用延迟量级建立位置感
延迟是 1 次操作从发起到获得所需结果的时间;带宽是单位时间能够传输的数据量。读取很小的数据常容易暴露固定等待,连续读取大量数据则更容易受到带宽限制。某设备顺序传输速度很高,并不意味着它对大量互不相邻的小请求也有很低的单次延迟。比较性能时,要先说明操作大小和访问方式。
下面给出常见系统的粗略量级,用于建立直觉。寄存器与 Cache 的时间受时钟频率、指令依赖和具体处理器影响;磁盘与网络的时间受队列、协议、距离和服务端处理影响。表内数值不是承诺值,也不能将不同机器上的测量直接拼成某台机器的完整路径。实际方案应以目标环境的测量替换示意值。
| 位置 | 典型访问延迟量级 | 理解时的边界 |
|---|---|---|
| 寄存器 | 亚 ns 至约 1 ns | 示意常见寄存器相关操作的量级,具体指令不同 |
| L1 Cache | 约 1 ns | 常见为数个 CPU 周期 |
| L2 Cache | 约数 ns | 通常比 L1 更大,访问更慢 |
| L3 Cache | 约 10–50 ns | 与芯片结构、核心位置和争用有关 |
| 主存 DRAM | 约 50–150 ns | 不包含所有可能的排队与软件开销 |
| 本地 SSD | 约数十至数百 μs | 示意小块随机读取,接口与负载影响显著 |
| HDD | 约数 ms 至十余 ms | 随机访问涉及寻道与旋转等待 |
| 网络访问 | 约数十 μs 至数百 ms 或更高 | 指不同范围的 1 次往返,范围跨度很大 |
1 μs 等于 1000 ns,1 ms 等于 1000 μs。若用 1 ns 代表靠近 CPU 的 1 次缓存读取,那么 100 μs 已经相当于 100000 个这样的时间单位。这个比例说明,节省少量指令可能无法抵消 1 次更远的取数。它不意味着 CPU 必然完全停住这么久,系统有时能通过并发、预取或执行其他工作掩盖部分等待。

网络放在表的末尾,是为了提示远程访问会引入额外路径,而非宣称任何网络都慢于 HDD。近距离、高性能网络的 1 次往返可能比 HDD 随机读取更短;跨地域请求则可能更长。网络本身也不是保存数据的介质。远程存储请求通常还要经过服务端软件与后端介质,完整延迟应按真实请求路径理解。
还要区分单次服务时间与排队后的响应时间。设备空闲时完成 1 次操作可能只需较短时间,但请求持续涌入后,等待服务的队列会增长。平均带宽尚有余量,也可能因为突发请求、热点或并发限制出现长尾。平均延迟只能描述分布的 1 个方面,面向用户的目标通常还需要观察 P95、P99 等百分位。
数量级表的实际用途,是帮助确定排查优先级。若 1 个小计算只需要少量 CPU 时间,却每次都向远端读取多个小对象,先检查请求次数、复用机会和并发约束通常更有意义。若数据已经稳定在内存中,程序仍然很慢,则应继续观察访问布局、主存带宽与 CPU 利用情况,不能把所有问题都归到网络上。
Cache 的基本机制:命中、缺失与行
当 CPU 查找的数据已经存在于所检查的 Cache 中,就称为命中;不存在则称为缺失。缺失之后,系统需要从更远的 1 层获取内容,再让当前访问继续。多级 Cache 允许不同位置发生命中,例如 L1 缺失而 L2 命中,不必立刻访问主存。具体处理器如何并行检查、填充和保留副本有不同实现,本课只使用逐级查找的教学模型。
Cache 通常以缓存行作为数据管理和传输的基本单位。许多常见处理器采用 64 B 的缓存行,但这不是所有设备的统一规定。假设某数组元素为 8 B,1 条 64 B 的行可以容纳 8 个连续且适当对齐的元素。首次读取其中 1 个元素时,附近元素可能一起进入 Cache,之后的访问就有机会直接命中。
是否跨越行边界也会影响访问。即使只读取相邻的 2 个元素,它们也可能恰好分属不同的行,需要分别取入。反过来,连续访问 8 个元素也不保证只涉及 1 条行,还取决于起始位置。这里引入对齐的目的,是提醒读者传输粒度与数据边界有关,不要求现在掌握地址位如何映射到具体缓存位置。

第 1 次读取某地址时,L1、L2、L3 可能都没有所需数据,最终由主存提供;下次读取可能已在 L1 命中。这个过程通常称为从冷到热的变化。实际填充哪些层、哪些副本可以同时存在,取决于处理器设计。交互只显示简化路径,不把“所有层必定持有同一份数据”当作普遍规则。
命中率必须带有统计口径。若共有 100 次处理器读取,其中 90 次在 L1 命中,L1 的命中率为 90%。剩余 10 次进入 L2 的查找,其中 8 次命中,则以到达 L2 的请求为分母,L2 的条件命中率为 80%;这 8 次只占最初全部请求的 8%。把不同分母的命中率直接相加,会得出错误结论。
可以用 1 个简化模型计算平均读取延迟。设在 L1、L2、L3、主存最终完成访问的总延迟分别为 1、4、12、80 ns,这些值已经包含到达对应位置之前的检查时间;各级条件命中率为 h1、h2、h3。则平均延迟为:
E = h1 × 1 + (1-h1) × [h2 × 4 + (1-h2) × (h3 × 12 + (1-h3) × 80)] ns。
这里没有再把 L1、L2 的检查时间额外相加,因为示意总延迟已经包含它们。也可以用逐层新增开销建模,但 2 种口径不能混用。各层最终完成概率之和应为 1,这是检查公式是否遗漏或重复计算某条路径的简单办法。该模型只描述串行、无争用的平均读取,不预测整段程序运行时间。
把 h1、h2、h3 分别设为 90%、80%、50%,最终在各位置完成的比例依次为 90%、8%、1%、1%,平均值为 2.14 ns。最后仅有 1% 的请求到达主存,却贡献 0.8 ns 的期望等待。少量高代价缺失足以影响平均值;而且同样的平均值可以对应不同的延迟分布,不能据此推导 P99。
写入还需要决定何时把修改传向更远处。写直达会让更新按策略继续传向更远层;写回则允许先在缓存中保留修改,之后在需要时写出。写回可以合并重复修改,但需要记录哪些行已被修改。实际系统可能配合写缓冲等机制,因此不能简单断言每次写直达都让 CPU 同步等待主存。本课只认识两者的基本取舍。
CPU Cache 通常不是断电后仍能保存数据的持久介质。写操作进入缓存、文件写入完成、数据达到可恢复的持久状态,属于不同层面的保证。业务若要求可靠保存,应使用相应存储系统提供的持久化语义,并确认成功返回代表什么。不能凭“已经写过内存”就判断故障后一定可以恢复。
从硬件金字塔走到云主机
在云主机中,CPU Cache 与主存仍然承担近处的计算与工作数据访问。本地盘、网络盘、对象存储则处于不同的访问路径。把它们放进同一张图,有助于判断请求会经过哪些部件,但不能将它们都当作 CPU 可以直接加载的同类存储。应用通常通过文件、块设备或服务接口访问持久数据。
本地盘位于宿主机附近,通常可以避免 1 次远程存储服务往返,但“本地”不自动说明介质类型、性能上限或数据生命周期。某些本地临时盘在实例释放或特定故障后无法保留数据,具体应查看产品约定。适合放入哪类数据,需要同时考虑可重建性、容量与故障恢复要求。
网络盘一般通过网络提供块设备能力,应用可能在其上建立文件系统。1 次读取可能涉及客户端、网络、远端服务和存储介质,实际响应时间受到多个环节影响。网络盘可以提供独立于某台计算主机的数据管理能力,但这并不意味着任何负载都能获得相同延迟。小块随机读与大块顺序读应分别评估。
对象存储以对象及其标识提供服务,常用于较大规模的持久数据。1 次对象请求通常包含协议处理、权限检查、服务端处理与数据传输,具体行为取决于服务接口。它适合什么访问模式,应从对象大小、请求次数、并发、首字节时间和传输带宽判断,不能只把它理解为“更远的一块硬盘”。

操作系统的文件页缓存使路径进一步变化。程序读取 1 个文件时,若所需内容已经位于主存中的页缓存,可能不必再次访问底层磁盘;未命中时才需要发起设备读取。因此测得“读盘很快”可能实际测到了内存缓存。CPU Cache 与文件页缓存可以同时起作用,但前者按缓存行管理,后者按页缓存文件数据,两者不能混称为同一命中率。
云端的存储金字塔因此更像 1 张带条件的访问图。应用内存缓存、文件页缓存、设备缓存和远端服务可能形成不同路径,某次请求也可能绕过其中部分层次。绘图时应标出实际使用的接口与命中位置。不能把产品介绍中各自最好的延迟数字串接起来,就当成自己业务请求的实测结果。
容易误判的几种“快”
缓存容量大,不等于任何程序都快。缓存保存什么、访问是否重复、带入的数据是否有用,都会影响效果。1 个只扫描 1 次的巨大文件,即使每次读取都经过缓存,也可能没有后续复用。更大的缓存可能延缓淘汰,却不能消除远大于容量且缺少复用的访问模式所带来的搬运成本。
内存与磁盘也不能因为都以 GB 标注容量就互相替代。主存主要为运行时访问提供空间,普通 CPU 指令可通过相应地址访问;磁盘通过设备与软件路径提供持久存储。内存不足时,某些系统可能把部分页面换到后备存储,但取回这些页面会增加等待。是否启用交换、交换到哪里,要以具体系统配置为准。
跨 AZ 访问不能按同机内存或本地盘理解。AZ 之间存在实际网络路径,还可能有流量费用、故障边界和服务部署约束。跨 AZ 可以服务于可用性目标,但请求链路上的每次远程依赖都需要进入延迟预算。即使 2 个组件位于同一地域,也不能省略对往返时间与尾延迟的测量。
命中率高,也不能单独证明用户体验良好。若多数请求很快,少数请求恰好连续缺失并串行访问远端,尾部用户仍可能等待很久。反过来,命中率较低的顺序扫描也可能通过大块传输获得较高吞吐。评价时应将命中率与延迟分布、请求大小、吞吐和资源利用情况放在一起解释。
还要警惕把所有读取都做成并发之后的结果当作单次读取延迟。并发可以重叠等待,提高整体吞吐,却不会让每个独立操作的物理路径消失。并发过高还可能导致排队增加。容量规划应区分单请求关键路径与系统同时处理的请求数量,不能用“总数据量除以峰值带宽”替代全部响应时间分析。
案例:顺序读与随机读
设有 1 个远大于 CPU Cache 的连续数组,每个元素占 8 B,缓存行按示意取 64 B。程序按地址递增扫描时,每取入 1 条行,后续通常还能使用其中其余元素。若改为在很大的范围内随机挑选元素,同一行中的其他元素可能很久都不会访问。即便 2 次执行读取的元素数量相同,真正搬运的数据量和有效利用比例也可能不同。
在忽略对齐边界与其他影响的粗略估算中,顺序读取可让 1 条行服务 8 次元素访问,分散随机读取则可能每次只用到 1 条行中的 1 个元素。后者为取得 8 B 有用数据,可能搬来 64 B。这个例子说明空间局部性的价值,不表示随机读取必然慢 8 倍;缓存命中、预取、并发与内存控制器行为都会影响结果。
如果把场景换成 HDD,顺序与随机差异还会包含机械运动。连续传输可以摊薄定位成本,分散的小块读取则可能反复付出寻道和旋转等待。SSD 没有这些机械动作,但随机小请求仍受请求处理、队列与设备内部机制影响。因此“SSD 适合随机访问”不能进一步推导为“访问顺序已经完全无关”。

分析此类现象时,可先保持数据规模与工作量一致,比较顺序和随机访问,再分别观察冷态与稳定运行阶段。应记录元素大小、访问次数、并发和是否覆盖相同范围。文件读取还要说明页缓存是否参与。没有这些条件,所谓“随机读取比顺序读取慢多少”的数字很难迁移到其他系统。
案例:工作集越过缓存容量
设 1 个服务反复扫描 1 组记录,简化缓存可容纳 4 条数据行。只循环访问 A、B、C 时,首次取入之后,后续访问可能大多命中;若改为循环访问 A、B、C、D、E,工作集就超过容量。在按最近使用情况淘汰的示意模型中,下次需要 A 时,A 已可能被淘汰,之后类似情况不断重复。

这个模型展示容量不足的 1 种极端效果。实际 CPU Cache 有具体放置与替换约束,不能仅靠总容量精确预测命中率,也不是所有程序越过容量边界都会突然变慢。若大部分访问集中于少数热点,即便全部数据远超缓存,热点仍可能保持较高命中率。判断时要看活跃访问分布,而非数据库总大小。
工作集的大小也不能只统计有效载荷。索引、对象头、指针和对齐填充会占用空间;同时运行的线程与其他任务可能竞争缓存。应用层缓存还可能保存反序列化后的对象,其大小不同于网络上传输的压缩数据。容量估算应明确采用哪种表示,并留出运行时管理与业务增长所需的余量。
可以通过缩小每次处理的范围,让数据在被淘汰前完成更多复用。例如把大批记录分成较小批次,在 1 个批次上完成多个相关处理步骤后再继续。这样的调整是否有效,仍取决于步骤之间是否真的复用相同数据,以及是否增加额外拷贝或破坏其他访问模式。原则是让活跃集合匹配近处资源,而不是机械地把所有批次都切得更小。
若观测到数据规模增长后延迟明显抬升,应先核对工作集、命中率与缺失代价是否同步变化,再判断是否需要扩容。只增加 CPU 核心数可能加大对共享缓存和内存带宽的竞争。可以考虑改善数据布局、减少重复读取、调整批次,或增加合适层级的容量;选择应由测量支持,而不是只按硬件名称升级。
放进方案里
随后画出请求路径,标出应用缓存、主存、本地盘、网络盘或对象存储,以及远程依赖是否跨 AZ。逐段记录请求大小、调用次数和典型延迟,区分串行等待与能够重叠的工作。命中率应标明分母,并说明数据来自实测还是估算。冷启动、稳定运行和缓存失效后的路径应分别描述,防止只用预热后的结果代表服务能力。
非功能需求中应写明测量条件与验收口径。例如在指定数据规模、并发和请求分布下约定端到端 P95、P99 延迟目标,同时规定持续吞吐。把总预算分给计算、缓存查找、存储读取和网络调用,并留出排队与波动余量。各段百分位不能简单相加得到端到端百分位,应通过完整链路测量验证整体目标。
若 1 次远程读取已经占据大部分预算,就应优先评估减少往返、批量读取或增加热点缓存;若问题来自工作集持续增长,则比较容量扩展、数据布局与分批处理的代价。每项措施都要说明适用条件、预期改善和验证指标。涉及持久数据时,还要写清本地临时数据的重建来源、成功写入的语义及故障后的恢复路径。
最后设置观测与复核条件:持续记录各层命中情况、读取延迟、队列与吞吐,在工作集或访问分布变化时重新评估预算。让容量和性能假设随业务数据更新,方案才能说明为什么当前配置足够,以及未来在什么条件下需要调整。

小结:用层次解释等待
存储层次用少量高速空间和大量较慢空间共同服务计算,局部性使这种安排具有实际价值。时间局部性带来近期复用,空间局部性提高相邻数据的使用比例。Cache 以行组织数据,命中减少向远处获取的机会,缺失则让访问承担更长路径的代价。缓存容量、访问方式和工作集需要放在一起判断。
解释“为什么慢”时,可以沿着数据路径依次确认:实际在哪层完成访问,传输了多少有效数据,远程往返发生了多少次,以及负载是否造成排队。数量级表帮助确定方向,命中率与延迟测量提供证据,完整请求链路决定最终用户看到的结果。任何单一指标都需要放回具体访问条件中理解。
接下来的 H11 将把注意力收回主存,进一步认识内存容量、地址与程序运行之间的关系。本课建立的层次视角仍然适用:先明确数据保存在哪里,再区分逻辑访问与实际搬运,最后判断容量和访问代价是否满足任务需求。
下一课转向 H11 内存基础:把本章验收过的结论带进后续主题,写成可检查的方案约束。