存深之墙
你以为cache够大就没有延迟问题。但DRAM访问延迟~100ns是电容充放电的物理极限,prefetch和一致性也救不了所有场景。内存墙不是bug,是物质的决定。
墙影降临
存深域的入口是一道峡谷。峡谷尽头矗立着一面墙——不是石墙,是无数细密裂缝拼成的暗影,像被速度差撕裂的断面。
墙上有声音,像很多数据包在排队,又像一个声音在数数。
「1980年到现在,CPU的速度翻了三万倍。DRAM的延迟呢?降了不到十倍。」
「打个赌吧。你说——cache够大,这面墙就不存在了?」
内存墙预测挑战器
先下注,再拖时间游标看CPU/DRAM双曲线,最后点亮五级延迟
CPU频率 vs DRAM延迟 · 1980-2024
延迟沙漏 · 五级存储层次
详解 · K14-01 内存墙:速度差的四十年
1980—2020 年,CPU 性能年均增长约 50%(频率×IPC 双重红利,累计约三万倍);同期 DRAM 延迟年均只降约 7%,四十年累计不到十倍。原因在物理层:DRAM 单元是「一个晶体管+一个电容」(1T1C),读一次要先把电容里微弱的电荷放大,再写回去补充——这套充放电流程的时间常数几乎不随工艺缩小。于是 CPU 等内存的时间占比越来越高:一次 DRAM 访问 ~80ns,3GHz 的核够执行约 240 拍。这条越来越宽的缝,就是内存墙。
详解 · K14-15 存储墙的真容:RC 时间常数
DRAM 的慢不是工程偷懒,是电容的 RC 充放电时间:位线预充、行激活(tRCD)、列选通(tCL)各有约 15ns 量级的物理下限,且电容太小会存不住电荷、太大会充放电更慢——两头堵。SRAM(cache)用六个晶体管正反锁存,不放电所以快,但面积大 6 倍以上、贵得离谱。所以存储层次永远存在:快的贵而少,慢的廉而多。墙拆不掉,只能绕着走、藏在底下——这是本章一切技术(cache、交错、预取)存在的理由。
镜像殿 · 副本之裂
石径尽头是一座镜像殿。殿中立着四面铜镜,每面镜子里映着同一行数据:A = 0。
「四个核心,各有一份cache副本。你让它们各读各的,看起来天下太平。」
「四份一样的副本,怎么可能不一致?你试试。」
缓存副本撕裂探索器
全读A → 各核写A=1 → 看其余镜面纹丝不动 → 触发stale read与lost update
时间轴 · 数据撕裂
详解 · K14-02 Cache 一致性的两条公理
一致性(coherence)只管同一个地址,定义两条:①写传播——一个核对 X 的写,最终要被其他核看到;②写串行化——所有核对 X 的写,在所有核看来顺序相同(不能 Core1 看到先 A=1 后 A=2,Core2 却看到先 A=2 后 A=1)。注意它不管「不同地址之间谁先谁后」——那是 S6 一致性模型(consistency)的事,一字之差,两层天堑。多核各带私有 cache 是性能必然(共享 L1 会被争抢拖垮),于是副本协调就成了硬件必须回答的问题。
窃听巷 · 监听之耳
镜像殿的后巷狭窄,头顶悬着一根铜线——总线。总线嗡嗡振动,像一条公共的神经。
「每次有核心读写内存,信号都会经过这根线。每个cache都能偷听——这叫监听。」
「偷听?你以为偷听就能一致?搭线吧。」
监听协议推导 · snoopy 基本原理
搭四根监听线 → 填两组监听规则 → 看撕裂愈合
详解 · K14-03 Snoopy 监听协议
Snoopy 协议 1980 年代随共享总线多处理器诞生(如 Intel 80486 的 MESI 前身),核心是搭总线的「便车」:总线本身是广播介质、天然串行化——每个 cache 控制器挂一对「监听耳」,看到总线上的地址就查自己的 tag 表。写操作触发两种流派:失效类(write-invalidate,把别人的副本标记作废,主流方案)与更新类(write-update,把新值推给所有副本,带宽开销大、已被淘汰)。你推导的两条规则——「见写作废」「持脏拦截」——正是失效类协议的两个基本动作。
详解 · K14-07 广播协议:全序的馈赠与其代价
广播(总线)协议的隐藏福利是天然全序:总线同一时刻只能承载一个事务,这个物理串行化自动给了所有核心一致的操作顺序——S6 的顺序一致性几乎免费。代价同样来自串行化:总线带宽恒定,事务量却随核心数增长,且每次写都要向全部 N 个 cache 发 invalidate、等 N 个 ack——S3 你量过这笔账。所以广播协议的命运早已写定:核少时是最佳方案,核多时必然撞墙(→ S5)。
四相阵 · MESI推演
巷子尽头是一座四相阵台。台上四个方位各亮着一盏空灯,灯下无字——等你来填。
「一致性的答案,不在书上,在你手上。你来推:一条cache行,到底有几种状态?」
MESI四相阵状态机推演器
推导M/E/S/I四状态 → 连八条转换线 → 看S→M广播代价
状态转换图 · 拖线连接
S→M 广播代价
详解 · K14-04 MESI:Illinois 协议的设计精髓
MESI 又称 Illinois 协议(Papamarcos & Patel,1984),四态的划分依据是两个正交问题:「这行只在我手里吗?」(独占/共享)×「我改过吗?」(脏/净)。E 态是最妙的一笔:独占且干净——若没有 E,「只有我有的干净行」只能算 S,那么第一次写它就必须广播 invalidate(尽管根本没有别人要通知);有了 E,E→M 的本地写零总线事务,单线程程序最常用的「读入再改写」路径完全静默。M 态则承担「唯一真值源」职责:它被替换或有人读之前,必须先把脏数据写回内存或直接转发——内存此时是过期的。
扩展阁 · 五相之争与原子之锁
扩展阁里,你S3的四相阵被原样搬来。但阵台旁多了一张空台,上有一盏未点亮的第五灯。
「S→M要广播invalidate,等所有人确认。还有一个问题——两个核心同时执行A++,你觉得结果是什么?」
「A++不是一条指令。它是读A、加一、写A——三步。两核同时做,丢一次更新,不是理所当然吗?」
协议扩展与原子操作 · 叙事推演
点亮第五灯F/O → 看原子RMW丢失更新 → 选锁方案
原子 RMW · A++ 三步演示
详解 · K14-05 MOESI 与 MESIF:第五态的两条路
两条扩展路线都针对 MESI 的真实痛点,且都量产过:MOESI(AMD Opteron/锐龙采用)的 O(Owned) 态允许「脏数据共享」——M 被读后降为 O,由 O 态 cache 直接供货,内存继续过期着也无妨,省掉一次写回;适合多核间频繁共享热点数据的场景。MESIF(Intel Nehalem 起的 QPI/UPI 时代)的 F(Forward) 态解决「N 个 S 态谁回答」的冗余:指定一个转发者,响应流量从 N 降到 1。五态的代价是状态机复杂度与验证成本——每加一个态,转换边数近似翻倍,这也是教科书先讲 MESI 的原因。
详解 · K14-10 原子 RMW:从总线锁到 LL/SC
A++ 的读-改-写三步必须不可分。硬件给两条路:①原子指令——x86 用 lock 前缀(如 lock xadd),近代实现锁 cache 行而非锁总线(早年的 bus lock 会让全机停顿);②LL/SC(链接加载/条件存储,ARM/RISC-V/MIPS)——load-linked 时记下地址并监听,store-conditional 仅当期间无人动过该行才成功,否则重试;它把「原子性」变成「冲突检测」,无锁数据结构(如无锁队列)全靠它。要留意的经典坑是 ABA 问题:值从 A→B→A 变回去,CAS 看不出来——解法是给指针附版本号(tagged pointer)。
广播狱 · 扩展性死壁
扩展阁的穹顶裂开,露出一排核心插槽——4、8、16、32、64。
「MESI?监听?加核啊。加到64。你觉得总线还撑得住?」
「推吧。自己看。」
广播目录扩展性曲线追踪器
推核心数4→64 → 看总线饱和崩塌 → 切目录协议看曲线恢复
详解 · K14-06 目录协议:用存储换扩展性
目录(directory)的思路:不再广播,而是在每个内存块的「家节点」(home)存一份谁持有副本的名册,写时按名册点对点发 invalidate。名册本身成了新瓶颈:全映射目录每块存 N bit 位向量——64 核时每块 64bit,1024 核时目录存储将超过数据本身;有限指针目录 Dirᵢ 只记 i 个指针,超出则广播兜底(i 取 4~8 已覆盖绝大多数共享场景);链式目录(如 SCI 标准)把名册串成链表分散到各 cache,省存储但失效延迟变长。目录还失去广播的「天然全序」,一致性排序要靠 ack 计数等机制补——这正是大规模一致性协议验证困难的开端。
时序殿 · 一致性的锚与隙
时序殿没有铜镜,只有一张巨大的时间轴横亘殿中。轴上排列着两个核心的读写操作,像两条并行的河。
「一致了——同一个地址,所有核心看到的值相同。但顺序呢?Core 0先写A=1再写B=1,Core 1先读B再读A——你保证B=1时A也一定=1吗?」
「一致性和一致性序,差一个字,差一道天堑。」
存储一致性模型 · SC与弱序
选一致性模型 → 看操作交错是否合法 → 看性能差异
详解 · K14-08 顺序一致性:Lamport 的严格定义
SC(Sequential Consistency)由 Lamport 于 1979 年定义:「多处理器并行执行的结果,等同于把各核操作按某种交错顺序串行执行,且每个核内部的操作顺序不变。」两个要点:①所有核看到同一个全局顺序(不是各自为政的合法交错);②程序顺序必须保留。SC 对程序员是天堂(直觉即正确),对硬件是镣铐:store buffer、invalidate queue、乱序提交这些提速结构天然破坏全局序,要严格 SC 就得大量 stall——这就是性能条上 SC 只有 30% 的原因。
详解 · K14-09 弱序与释放一致性:按「是否同步」分类松绑
弱一致模型的洞察:数据竞争只发生在没走同步的访问上。把访存分成「同步操作」(加锁/解锁、flag 读写)与「普通数据操作」——后者随便重排,前者必须严格有序:release(释放/解锁)保证它之前的写先可见,acquire(获取/加锁)保证它之后的读不被提前。你放置 fence 恢复「W(A) 先于 W(B) 可见」,正是 release 语义。现实中的 x86-TSO 介于 SC 与弱序之间:允许「写后读」被 store buffer 重排(唯一放松的一种),所以 x86 程序员有时仍需 mfence;ARM/RISC-V 则是彻头彻尾的弱序,C++11 的 memory_order 体系正是为在这光谱上写可移植代码而生。
交错阵 · 带宽之舞与优化之术
交错阵台上排列着八块内存bank,像八条平行的河道。
「一次访存只走一条bank,其余七条闲着。你把连续地址分散到不同bank——让它们并行流。这叫交错。」
「带宽翻倍?你试试。但——你能预取的,只有你能预测的。」
交错·预取·编译器优化 · 三把刀
切交错模式看带宽 → 切访问模式看预取污染 → 行/列优先看miss率
预取准确率
编译器优化 · 行/列优先
详解 · K14-11 存储交错:把串行延迟摊成并行流水
DRAM 一次访问的 80ns 里,真正传数据只占末尾几 ns,前面都是行激活与列选通的「占位时间」。低阶交错(连续地址按 bank 轮发,A%8)让 8 个 bank 的占位时间互相重叠:第一个数据回来后,其余 7 个按节拍接连抵达——带宽 ×8,这正是 DDR「突发传输」(burst)与多通道内存(双通道带宽翻倍)的原理。陷阱是bank 冲突:stride=8 的访问全落在同一 bank,每次都要等上一次完全结束——带宽跌回 1×。所以结构体数组(AoS)改数组结构体(SoA)、矩阵访问改布局,本质都是在躲 bank 冲突。
详解 · K14-14 预取与编译器优化:预测的艺术
硬件预取器守在 cache miss 流上找规律:stride 预取器检测等差序列(+64B、+64B……)就提前取后续行——顺序访问下准确率 95%+;但指针追逐(p->next)的地址无规律,预取不仅白取,还把有用行挤出 cache(污染 70%)。编译器侧的对应武器是改造访存模式本身:循环交换(把按列的内层循环改成按行)、循环分块 tiling(把大矩阵切成小块,块内数据能驻留 L1/L2 再复用)、数组合并与填充对齐。行优先遍历 miss 3% vs 列优先 87%——同一个算法,8 倍性能差,这就是「cache-aware 编程」的含金量。
存域回廊 · NUMA与持久之层
回廊两侧是两面巨大的全景图。左图:四个NUMA节点,每个节点有自己的本地内存和四个核心。右图:存储层次金字塔——顶端CPU寄存器,向下L1/L2/L3 cache,再向下DRAM,最底层SSD。
「左边:你的核心访问本地内存~80ns,访问隔壁节点的内存~150-300ns——这叫NUMA。右边:DRAM和SSD之间,多了一层——非易失性内存。」
NUMA延迟地图 · PM新层
点NUMA节点看local/remote延迟 → 拖入PM层看新维度
详解 · K14-12 NUMA:内存的地理学
多路服务器里,每个 CPU 插槽挂着「自己的」内存条——本地访问走片外内存控制器 ~80ns;访问对家插槽的内存要走插槽间互连(Intel UPI / AMD Infinity Fabric),延迟 150~300ns,带宽还打折。这就是 NUMA(非一致内存访问)。操作系统的对策是 first-touch 策略:哪个核先写这块页,页就分配在哪个节点——所以高性能程序要配合线程绑核(affinity)与 numactl,让「线程和它摸的内存同处一个节点」。数据库与科学计算的 NUMA 失配曾造成 2~4 倍的性能谜团,元凶都是跨节点流量。
详解 · K14-13 持久内存(PM):DRAM 与 SSD 之间的新大陆
持久内存(以 Intel Optane DIMM 的 3D XPoint 为代表)曾给出诱人组合:字节寻址(像内存,不像 SSD 按 4KB 块读写)、断电不丢、延迟 ~300ns(比 DRAM 慢 3 倍,比 SSD 快 300 倍)、单条容量可达 512GB。两种用法对应你点的两个按钮:Memory Mode 拿 DRAM 当 PM 的 cache,软件零改动获得大内存;App Direct 让数据库直接 mmap 持久写,省掉 write-ahead log 刷盘。注:Optane 已于 2022 年停产退场——但「内存与存储之间需要持久层」的思想活了下来,正以 CXL 内存扩展与高速 NVMe 的形态延续。
Boss 战 · 内存墙之影
回廊灯火齐灭。暗影从四面八方压过来,凝成一面巨大的、布满裂缝的墙。
「我是内存墙之影。我不住在你的无知里——无知救不了我。我住在你'以为优化能消灭物理极限'的地方。你每以为加一层cache、开一个预取就能填平我,我就厚了一寸。三问。答错一题,墙就厚一分。」
渊账累计 0 笔 · 三问全对后清零
尾声 · 破墙之光
墙影凝固成DRAM单元结构后,峡谷透进一缕金光。你手里的延迟沙漏还在——CPU侧金沙飞落,DRAM侧沙粒迟滞。差距仍在,但你不害怕了。
「你入门了。不是因为你答对了三问——答对的会忘。是因为你摔过的每一道裂痕,都刻在你手上了。但——这些核心,到底怎么连在一起?总线?互连网络?片上网络?你说说,64个核心要怎么通信才不堵?」
知识图谱点亮 · 存储墙权衡
点亮全章15块碎片 → 看存储层次延迟谱全景
复盘 · 存深域全图:从撕裂到修路
本章走了一条「信心三度崩塌」的路:第一崩(S1-S2)——以为各核有副本就太平,撕裂教你监听;第二崩(S3-S5)——以为 MESI 一劳永逸,64 核的 O(N²) 教你目录与「没有银弹」;第三崩(S6-S7)——以为一致即有序、优化即拆墙,SC/弱序的天堑与三把刀的边界教你:优化只能隐藏墙,不能消灭墙。最后(S8)你学会了在墙下修路的全景手艺:NUMA 选位置、PM 选层次。下一章的问题已经抛出——64 个核心不靠总线,靠什么互连?片上网络(NoC)在 ch15 等你。