Skip to content

TLB的设计

某互联网公司的DBA发现了一个令人费解的性能异常:同一条SQL查询在裸机上运行200ms,但在同规格的虚拟机中运行时间波动剧烈——从220ms到800ms不等。CPU并不忙,I/O延迟也在正常范围内。直到perf计数器揭示了真相:dTLB-load-misses事件的计数从裸机的0.1%飙升到虚拟化环境的3.5%。数据库的B+树索引遍历和哈希表探测产生大量随机内存访问,每次TLB Miss在虚拟化的嵌套页表遍历下消耗超过800个周期。更糟糕的是,由于多个虚拟机共享TLB,有效TLB容量被稀释到不足裸机的四分之一。将数据库的堆内存从4KB小页切换为2MB大页后,TLB Miss率降至0.05%,查询延迟回落到210ms——一个简单的页大小配置变更,消除了4倍的性能差异。

统一视角连接。 本书的核心论点是:处理器设计的本质是在有限的晶体管预算和功耗约束下,通过投机和并行的层层叠加来逼近指令吞吐率的理论上限。TLB正是这一哲学在地址转换领域的集中体现——它是对地址翻译结果的投机缓存,处理器投机地假设最近翻译过的虚拟地址映射在短期内不会改变,从而将每次需要4–20次内存访问的页表遍历"压缩"为1个周期的CAM查找。这种投机在绝大多数时候是正确的——TLB的命中率通常在99%以上——但当投机失败时(TLB Miss),代价从第 10.0 章中讨论的4次内存访问(裸机)放大到第 12.0 章将要讨论的24次访问(虚拟化环境)。TLB的设计质量直接决定了这种投机的成功率,一个设计不当的TLB可以使处理器的有效IPC下降30%以上。

第 10.0 章中,我们讨论了虚拟存储器的基本概念和页表结构。页表存放在主存中,每次虚拟地址到物理地址的转换都需要多次内存访问(例如四级页表需要4次),这使得不经优化的地址转换延迟高达200–400个周期——与一次LLC缺失的惩罚相当。TLB(Translation Lookaside Buffer)正是为解决这一问题而引入的硬件缓存结构:它将最近使用的页表项缓存在处理器内部,使得绝大多数地址转换可以在1–2个周期内完成。本章详细讨论TLB的微架构设计,包括其基本结构、多级层次、多页面大小支持、管理机制(包括核间TLB Shootdown同步)、TLB预取技术,以及与Cache的配合方式。

TLB的基本结构

TLB本质上是一个小容量、高速的地址转换缓存。它以虚拟页号(VPN)为键、物理页帧号(PPN)及相关属性为值,在每次访存时快速完成地址转换。从第 5.0 章中讨论的Cache设计视角来看,TLB与数据Cache具有深刻的结构同构性——两者都是用小容量高速存储来缓存大容量低速存储的查询结果。差异在于:数据Cache缓存的是内存中的数据块,而TLB缓存的是页表中的地址映射。这一同构意味着Cache设计中的许多概念(全相联vs组相联、替换策略、多级层次、预取)都可以直接应用于TLB设计,但TLB又有其独特的约束——它位于所有Cache访问的关键路径之前,延迟要求比任何Cache都更苛刻。TLB的组织方式决定了其命中延迟、面积开销和命中率三者之间的权衡。

全相联TLB

全相联(Fully Associative)TLB是最直接的设计方式:每个TLB表项可以存放任意VPN到PPN的映射,查找时将请求的VPN与所有表项并行比较。这种设计的核心硬件结构是CAM(Content-Addressable Memory),也称为关联存储器。

CAM结构的基本原理

在CAM中,每个存储单元不仅可以读写数据,还内置了比较逻辑。当输入一个搜索键(search key)时,所有存储单元同时将自身存储的值与搜索键进行比较,并输出匹配结果。图图 11.1展示了一个典型的TCAM(Ternary CAM)单元的简化结构。

全相联TLB的CAM结构示意
全相联TLB的CAM结构示意

CAM单元的面积开销远大于普通SRAM单元:如第 3.0 章中讨论的,一个6T SRAM单元在5nm工艺下仅占用约0.03μm20.03\,\mu\mathrm{m}^2,而一个CAM单元由于内置比较逻辑(每位需要额外的XOR门和匹配线下拉晶体管),面积约为SRAM单元的2.5–3倍。在先进工艺节点下,CAM的面积优势比率趋于恶化——因为SRAM可以充分利用高密度标准单元库,而CAM的比较逻辑需要定制布局。此外,所有CAM单元在每次查找时同时翻转比较线(match line),功耗也显著高于SRAM。这一面积和功耗约束是TLB容量难以像Cache那样轻松扩大的根本物理原因。

全相联TLB的优缺点

全相联TLB的最大优势在于命中率最高。由于任何VPN都可以映射到任何表项,不存在冲突缺失(conflict miss),在相同容量下的命中率严格不低于其他组织方式。对于L1 TLB而言,这一优势至关重要——L1 TLB的容量通常只有32–72项,每一项的利用效率都必须最大化。

全相联TLB的主要缺点是面积和功耗大。对于NN项的全相联TLB,每次查找需要NN个比较器同时工作,功耗与NN成正比。当NN超过128–256时,CAM的面积和功耗开销变得难以接受。因此,全相联组织通常仅用于容量较小的L1 TLB。

性能分析 1 — 全相联TLB的功耗估算

考虑一个64项全相联L1 dTLB,每项存储48位VPN(假设48位虚拟地址、4KB页,VPN为36位加上ASID等共48位比较宽度)。每次查找需要64×48=307264 \times 48 = 3072个比较位同时翻转。在5nm工艺下,每个比较位翻转约消耗0.5fJ0.5\,\mathrm{fJ},则每次查找的动态功耗约为3072×0.5fJ=1.54pJ3072 \times 0.5\,\mathrm{fJ} = 1.54\,\mathrm{pJ}。假设处理器运行在4GHz4\,\mathrm{GHz},每周期进行一次dTLB查找,则dTLB的动态功耗约为1.54pJ×4×1096.2mW1.54\,\mathrm{pJ} \times 4 \times 10^9 \approx 6.2\,\mathrm{mW}。虽然绝对值不大,但如果L2 sTLB也采用全相联且容量为1024项,功耗将增长16倍至约100mW100\,\mathrm{mW}——这就不可接受了。

现代处理器的L1 TLB几乎一律采用全相联组织。例如:

  • Intel Golden Cove(Alder Lake P-core):L1 iTLB 256项(8-way,支持4KB/2MB/1GB页),L1 dTLB 96项全相联;

  • AMD Zen 4:L1 iTLB 64项全相联,L1 dTLB 72项全相联;

  • ARM Cortex-X4:L1 iTLB 48项全相联,L1 dTLB 48项全相联;

  • RISC-V SiFive P870:L1 iTLB 48项全相联,L1 dTLB 44项全相联。

TLB的替换策略

全相联TLB的替换策略直接影响命中率。常见选择包括:

  • 伪LRU(Pseudo-LRU):使用树形结构(tree-based PLRU)近似最近最少使用策略。对于NN项全相联TLB,只需N1N-1位状态即可维护。例如64项TLB仅需63位。PLRU的命中率接近真正的LRU,但硬件复杂度显著降低。大多数现代处理器的L1 TLB采用此策略。

  • Not-Recently-Used(NRU):维护每项的一个"最近使用"位,替换时从"最近未使用"的表项中随机选择。实现最简单,但命中率略低于PLRU。

  • 轮转(Round-Robin):按固定顺序依次替换各表项。在某些嵌入式处理器中使用,命中率最低但实现复杂度最小。

在实际工作负载中,PLRU与真正LRU的命中率差距不到0.5%,但PLRU的更新逻辑(每次命中时更新log2N\log_2 N个位)比LRU(每次命中时更新N1N-1个位的排序)简单得多,因此PLRU是事实上的标准。

为什么不用真正的LRU?一个NN项的精确LRU需要维护NN个条目的完全排序,每次访问可能需要更新所有NN个条目的排序信息(最坏情况:最近最少使用的条目被访问,所有其他条目的排序都需要调整)。对于64项TLB,精确LRU需要64×log264=38464 \times \lceil\log_2 64\rceil = 384位的状态信息,每次访问可能更新全部384位。而树形PLRU仅需641=6364 - 1 = 63位,每次访问更新log264=6\log_2 64 = 6位——状态量减少6倍,更新量减少64倍。在面积和功耗极度受限的L1 TLB中,这一差异是决定性的。学术研究表明,PLRU在TLB中的命中率与LRU的差距通常不超过0.3%——对于一个98%命中率的TLB,这意味着PLRU的命中率为97.7%,差异微乎其微。

对于支持大页的TLB,替换策略还需要考虑大页表项的价值。一个2MB大页的TLB表项覆盖的地址范围是4KB页的512倍,因此替换一个2MB大页的表项所引起的后续缺失数量可能远多于替换一个4KB页的表项。某些实现通过为大页表项分配更高的"保留优先级"来减少大页表项被过早替换的概率。

组相联TLB

当TLB容量增大到数百甚至数千项时,全相联的CAM面积和功耗变得不可接受。此时可以采用组相联(Set-Associative)组织来折中。

在组相联TLB中,VPN的低位若干位用于索引选择一个组(set),然后仅在该组内的若干路(way)之间进行并行比较。例如,一个1024项、8-way的TLB有128个组,VPN的低7位用于索引,每次查找只需8个比较器而非1024个。

组相联TLB的结构示意
组相联TLB的结构示意

组相联TLB的关键参数是路数(associativity):路数越高,冲突缺失越少,但每次查找所需的比较器越多,延迟和功耗也随之增加。表表 11.1展示了不同路数在1024项TLB上的权衡。

路数组数比较器数量/次查找相对面积相对功耗
4-way25641.0×\times1.0×\times
8-way12881.15×\times1.4×\times
12-way85121.25×\times1.8×\times
16-way64161.40×\times2.3×\times
全相联110243.5×\times16×\times

1024项TLB在不同路数下的权衡(5nm工艺估算)

现代处理器的L2 sTLB(Shared/Second-Level TLB)普遍采用组相联组织。例如Intel Golden Cove的L2 sTLB为2048项、16-way组相联,AMD Zen 4为2048项、8-way组相联,ARM Neoverse V2为2048项、8-way组相联。从表表 11.1可以看出,8-way在面积和功耗上仍然可控,同时提供了接近全相联的命中率——这也是为什么8-way成为L2 sTLB最常见的选择。

组相联TLB的哈希索引

组相联TLB的索引函数选择也值得关注。最简单的索引方式是直接使用VPN的低位,但这在某些地址模式下会导致严重的冲突缺失——例如,如果程序同时访问多个数组,且这些数组的起始地址在VPN低位上对齐,它们就会映射到同一个组,导致反复驱逐。

更高级的设计使用哈希索引:将VPN的不同部分通过XOR运算组合后作为组索引。例如将VPN[12:7]与VPN[18:13]进行异或来生成6位索引。哈希索引可以打破规律性的地址分布,降低冲突缺失率。实测表明,哈希索引可以在某些工作负载上将L2 sTLB的缺失率降低5–15%。

Intel的L2 sTLB使用了一种未公开的哈希函数来分散TLB表项,AMD同样在L2 sTLB中使用了XOR-based哈希索引。

设计提示

组相联TLB的路数选择遵循一个经验规则:L1 TLB(32–96项)使用全相联,L2 sTLB(512–4096项)使用4–16路组相联。当L2 sTLB容量超过2048项时,16-way通常是上限,因为更高路数带来的命中率提升不足以弥补面积和延迟的增加。

TLB的功耗优化技术

TLB虽然容量小,但由于每个时钟周期都可能被访问(取指和访存各一次),其动态功耗在总处理器功耗中占比不可忽略。以下是几种常见的TLB功耗优化技术:

  1. 分阶段CAM匹配:不一次性比较VPN的全部位,而是先比较VPN的低位(例如低12位),仅对低位匹配的表项再比较高位。这种两阶段匹配可以在第一阶段就排除大部分不匹配的表项,减少第二阶段的比较器翻转次数。代价是在最坏情况下延迟增加一个周期。

  2. 选择性CAM使能:对于组相联TLB,索引选组后仅使能被选中组的CAM单元进行比较,其余组的CAM单元保持不变(门控时钟或使能信号控制)。这直接将CAM的动态功耗降低了NsetsN_{\text{sets}}倍。

  3. TLB banking:将TLB分为多个bank,每次查找只激活包含目标组的bank。例如4-bank的设计可以将功耗降低约70%(考虑bank选择逻辑的开销)。

  4. 微TLB(Micro-TLB):在全功能TLB之前放置一个极小的缓存(4–8项),捕获最近几次地址转换的结果。如果微TLB命中,全功能TLB不需要被激活。在具有强时间局部性的访存模式下,微TLB可以拦截60–80%的查找请求。

TLB表项的格式

TLB表项(entry)不仅仅是VPN到PPN的简单映射,还包含了页表项中的各种属性信息。图图 11.3展示了一个典型的TLB表项格式。

TLB表项的典型格式
TLB表项的典型格式

各字段的含义和设计考量如下:

VPN(虚拟页号)

这是TLB查找时的主要匹配键。VPN的宽度取决于虚拟地址空间大小和页大小。以48位虚拟地址、4KB页为例,VPN为4812=3648 - 12 = 36位。如果支持57位虚拟地址(如x86-64的5级页表或RISC-V Sv57),VPN宽度增加到45位。VPN越宽,CAM的面积和功耗开销越大。

ASID(Address Space Identifier)

ASID用于区分不同进程的地址空间。没有ASID时,每次进程切换都需要刷新整个TLB,因为不同进程的相同VPN映射到不同的PPN。加入ASID后,不同进程的TLB表项可以共存于同一个TLB中,匹配时同时比较VPN和ASID,从而避免了进程切换时的TLB刷新开销。

ASID的宽度因ISA而异:x86-64的PCID(Process-Context Identifier)为12位,最多区分4096个进程;ARM的ASID为8位或16位(由TCR_EL1.AS位控制);RISC-V的ASID在Sv39/Sv48下为16位。更宽的ASID意味着更多进程可以共存于TLB中而不发生冲突,但也增加了CAM的宽度。

PPN(物理页帧号)

这是TLB查找的输出——命中后,PPN与页内偏移拼接即得物理地址。PPN的宽度取决于物理地址空间大小和页大小。以52位物理地址(如x86-64支持的最大物理地址宽度)和4KB页为例,PPN为5212=4052 - 12 = 40位。

页大小字段(PS)

现代处理器支持多种页大小(4KB、16KB、2MB、1GB等),TLB表项中需要记录该表项对应的页大小。页大小信息影响VPN匹配的方式:对于2MB大页,VPN的低9位(对应4KB页的页内偏移部分)应被忽略。实现上通常用2–3位编码页大小,或者在比较时通过掩码(mask)屏蔽VPN的相应低位。

权限和保护位

包括:

  • R/W/X:读/写/执行权限。在每次TLB命中时与当前访问类型比较,不匹配则触发保护异常。

  • U:用户模式可访问位。控制用户态是否可以访问该页。

  • G:全局位。标记为全局的页(如内核映射)不与ASID关联,在所有进程中共享。TLB查找时,如果表项的G位为1,则跳过ASID的比较。

  • D(Dirty):脏位。页被写入后置1,用于操作系统的页面替换决策。

  • A(Accessed):访问位。页被读取或写入后置1,同样用于页面替换。

内存类型(MemType)

指定该页的Cache策略,常见类型包括:

  • WB(Write-Back):正常可缓存内存,大多数程序代码和数据使用此类型;

  • WT(Write-Through):写直通,用于需要强一致性的场景;

  • UC(Uncacheable):不可缓存,用于MMIO设备寄存器;

  • WC(Write-Combining):写合并,用于帧缓冲等可以批量写入的设备内存。

虚拟化扩展字段

在虚拟化环境中(将在第 12.0 章详细讨论),TLB表项需要额外的字段:

  • VMID(Virtual Machine Identifier):用于区分不同VM的地址映射。典型宽度为8–16位。没有VMID时,每次VM切换都需要刷新整个TLB。

  • Guest/Host标记:区分该条目是Guest的翻译(GVA\toHPA)还是Host的翻译(HVA\toHPA)。

VMID的引入使TLB表项宽度增加了8–16位。对于一个使用全相联CAM的L1 dTLB,每多一位比较宽度意味着每次查找多NN个比较位翻转(NN为TLB项数)。以72项TLB为例,增加16位VMID使每次查找的动态功耗增加约72×16×0.5fJ=576fJ72 \times 16 \times 0.5\,\mathrm{fJ} = 576\,\mathrm{fJ},占总查找功耗的约37%——这是虚拟化对TLB硬件的一个不可忽视的"功耗税"。

TLB表项的总宽度因ISA和实现而异,典型值在80–120位之间(非虚拟化)或98–136位(虚拟化)。表表 11.2给出了几种常见配置下的估算。

配置VPNASIDPPNPS权限MemType总计(约)
x86-64 (4级)36b12b40b2b5b3b98b
x86-64 (5级)45b12b40b3b5b3b108b
AArch6436b16b36b2b7b4b101b
RISC-V Sv4836b16b32b2b5b2b93b

TLB表项宽度的典型配置

多级TLB

与Cache层次结构类似,现代处理器采用多级TLB来平衡速度和容量。这种分层设计的第一性原理来自于工作集的时空局部性:程序在短时间内密集访问的页面数量远少于整个工作集中的页面数量。L1 TLB追求最低延迟(1周期),容量较小(32–160项),捕获最近和最频繁访问的页面映射;L2 TLB追求覆盖率,容量较大(1024–4096项),以更高的延迟(4–8周期)提供第二次命中机会。某些高端设计甚至引入了L3 TLB或更大的"page walk cache"来进一步扩展地址转换的覆盖范围。

硬件描述 1 — TLB层次结构的带宽需求分析

在一个6-wide超标量处理器中,每周期最多可以有2条Load指令和1条Store指令同时执行(典型配置),加上取指的1次iTLB查找,TLB系统在峰值时每周期需要处理4次地址转换请求。L1 dTLB至少需要2个读端口(供2条Load并行查找),L1 iTLB需要1个读端口。

当L1 TLB发生Miss时,请求被转发到L2 sTLB。如果L1 dTLB的缺失率为2%,每周期平均有3×0.02=0.063 \times 0.02 = 0.06次L2 sTLB查找请求。L2 sTLB的1个读端口足以应对这一带宽需求。但在高TLB缺失率的工作负载中(如数据库随机访问),L1 缺失率可能达到5–10%,L2 sTLB的访问频率显著增加,可能需要流水线化的L2 sTLB来保持吞吐量。

L1 iTLB和dTLB

L1 TLB分为两个独立的结构:iTLB(Instruction TLB)用于取指阶段的地址转换,dTLB(Data TLB)用于加载/存储阶段的地址转换。分离设计的原因与L1 Cache分离的原因一致——消除取指和访存之间的结构冲突,允许两者同时进行地址转换。

iTLB的设计特点

iTLB位于取指流水线的最前端,其延迟直接影响取指带宽。在超标量处理器中,取指单元每周期需要从连续的虚拟地址取出一个Cache行(通常为64字节)的指令,因此iTLB必须在1个周期内完成查找并返回物理地址,以便同周期或下一周期访问L1 I-Cache。

iTLB的容量通常为32–64项。由于程序的代码局部性通常优于数据局部性(代码倾向于在较小的热点区域内执行),iTLB的命中率即使在较小容量下也能维持在99%以上。但存在例外:大型服务器应用(如数据库引擎、JIT编译器)的代码工作集可能超过数十MB,此时iTLB缺失率可能攀升到1–3%。

dTLB的设计特点

dTLB位于访存流水线的地址生成(AGU)之后、Cache查找之前(在PIPT组织下)或与Cache查找并行(在VIPT组织下)。dTLB的延迟直接决定了加载指令的延迟——L1 dTLB命中时,加载指令的总延迟通常为4–5个周期(地址生成1周期 + TLB查找1周期 + Cache查找2–3周期)。

dTLB通常比iTLB稍大,容量在48–96项之间。数据访问的局部性不如代码,且数据的工作集通常远大于代码,因此dTLB需要更多表项来维持高命中率。

多页大小的支持

L1 TLB需要同时支持多种页大小。有两种常见实现方式:

  1. 统一存储:所有页大小的映射存储在同一个TLB阵列中,每个表项携带页大小信息,查找时通过掩码动态调整VPN的比较范围。这种方式灵活性最高,但掩码比较增加了时序路径的复杂度。

  2. 分离存储:为不同页大小维护独立的TLB阵列。例如一个dTLB可能有64项4KB页的表项和32项2MB大页的表项,查找时并行搜索两个阵列。这种方式时序更简洁,但需要预先分配各页大小的容量,在工作负载特征变化时可能出现一种页大小的表项耗尽而另一种页大小的表项空闲的情况。

Intel从Skylake开始在L1 dTLB中采用分离存储,将4KB页和2MB/1GB页分开存放。AMD Zen 4的dTLB也采用类似设计:72项用于4KB页(全相联),8项用于2MB页(全相联),8项用于1GB页(全相联)。ARM Cortex-X4的dTLB为48项全相联,支持4KB/16KB/64KB/2MB页的混合存储。

案例研究 1 — Intel Golden Cove的L1 TLB

Intel Golden Cove(Alder Lake P-core)的L1 TLB配置如下:

  • L1 iTLB:256项,8-way组相联,支持4KB和2MB页。对于1GB页,使用2MB页的映射方式(将一个1GB页拆分为512个2MB页的TLB表项)。iTLB的8-way设计是一个值得注意的选择——大多数竞争对手的iTLB采用全相联,而Intel选择组相联是为了在增大容量(256项远大于典型的48–64项)的同时控制延迟。

  • L1 dTLB:96项全相联,分为三个独立的子阵列——64项用于4KB页、32项用于2MB/1GB页。dTLB保持全相联是因为数据访问的局部性不如代码,冲突缺失的代价更高。

这一设计体现了Intel在iTLB和dTLB上采用不同策略的思路:iTLB追求大容量(通过组相联控制面积),dTLB追求高灵活性(通过全相联避免冲突缺失)。

L2 sTLB

当L1 iTLB或dTLB缺失时,请求被转发到L2 sTLB(Second-level Shared TLB,也称为STLB或unified TLB)。L2 sTLB是指令和数据共享的统一TLB,容量远大于L1 TLB,用于在触发昂贵的页表遍历之前提供第二次命中机会。

L2 sTLB的典型参数如下:

  • 容量:512–4096项。主流高性能处理器多为1024–2048项。

  • 组织方式:4–16路组相联。全相联在此容量下不可行。

  • 命中延迟:4–8个周期。L2 sTLB的时序约束比L1宽松,可以使用更传统的SRAM+比较器结构。

  • 大页支持:L2 sTLB通常采用统一存储方式(所有页大小混合存储),通过掩码比较来处理不同页大小。

L2 sTLB与L1 TLB之间的包含关系(inclusion policy)是一个重要设计决策:

  • 包含式(Inclusive):L2 sTLB严格包含L1 TLB的所有表项。优点是L2 sTLB缺失意味着两级TLB都缺失,简化了控制逻辑;缺点是有效容量等于L2 sTLB的容量,L1 TLB的表项在L2中有冗余副本。

  • 排他式(Exclusive):L1 TLB和L2 sTLB的表项不重叠,有效容量为两者之和。L1 TLB被替换的表项"下沉"到L2 sTLB。优点是有效容量更大;缺点是控制逻辑更复杂,特别是需要处理L1 TLB miss但L2 sTLB hit时的表项交换。

  • 非包含非排他(NINE):不维护严格的包含或排他关系,灵活管理。大多数现代处理器采用此策略。

TLB覆盖范围(TLB Reach)

TLB Reach是指TLB能够覆盖的虚拟地址空间大小,等于TLB表项数乘以页大小。TLB Reach是衡量TLB有效性的关键指标——如果程序的工作集超过了TLB Reach,TLB缺失率将急剧上升。表表 11.3展示了不同配置下的TLB Reach。

TLB配置4KB页16KB页2MB页1GB页
L1 dTLB (72项)288KB1.1MB144MB72GB
L2 sTLB (2048项)8MB32MB4GB2TB
L1+L2合计 (2120项)8.3MB33MB4.1GB2.1TB
L2 sTLB (4096项)16MB64MB8GB4TB
L1+L2合计 (4168项)16.3MB65MB8.1GB4.1TB

不同TLB配置下的TLB Reach

从表表 11.3可以清楚地看到,4KB页下的TLB Reach极为有限——即使L2 sTLB有4096项,总覆盖范围也仅为16MB,远不及现代服务器应用的工作集(数百MB到数十GB)。这就是为什么大页(Huge Page)如此重要:使用2MB页时,同样的2048项L2 sTLB可以覆盖4GB的地址空间,足以满足大多数应用的需求。

设计权衡 1 — L2 sTLB容量的选择

增大L2 sTLB的容量可以降低页表遍历的频率,但带来的收益呈递减趋势。实测数据表明,对于SPEC CPU 2017等典型桌面/服务器工作负载:

  • 512项L2 sTLB的命中率约为85–92%;

  • 1024项约为90–95%;

  • 2048项约为93–97%;

  • 4096项约为95–98%。

从1024项增加到2048项,命中率提升约2–3个百分点,减少的页表遍历次数约为每千条指令0.1–0.3次。假设每次页表遍历的代价为30–50个周期(L2 Cache命中情况下),则节省的周期约为每千条指令3–15个周期,对应IPC提升约0.5–2%。这一增益在高端处理器中仍然是值得追求的,但对面积敏感的移动处理器可能会选择较小的容量。

性能分析 2 — TLB AMAT(Average Memory Access Time)的层次化分析

Setup. 构建一个完整的TLB层次化AMAT模型,量化各级TLB对平均地址翻译延迟的贡献。

Strategy. 使用递归AMAT公式,从L1 TLB到PTW逐级展开。

Derivation. TLB的AMAT可以表示为:

AMATTLB=tL1+rL1×(tL2+rL2|L1×TPTW) \text{AMAT}_{\text{TLB}} = t_{\text{L1}} + r_{\text{L1}} \times \bigl(t_{\text{L2}} + r_{\text{L2|L1}} \times T_{\text{PTW}}\bigr)

其中tL1t_{\text{L1}}是L1 TLB的命中延迟(1周期,已包含在流水线中故记为0额外周期),rL1r_{\text{L1}}是L1 TLB缺失率,tL2t_{\text{L2}}是L2 sTLB的命中延迟(6周期),rL2|L1r_{\text{L2|L1}}是L2 sTLB的条件缺失率(在L1缺失的请求中进一步缺失的比例),TPTWT_{\text{PTW}}是页表遍历的平均延迟。

考虑两种场景:

  • 低压力场景(SPEC CPU整数):rL1=1%r_{\text{L1}} = 1\%rL2|L1=10%r_{\text{L2|L1}} = 10\%TPTW=35T_{\text{PTW}} = 35周期(有PWC加速)。
AMATTLB=0+0.01×(6+0.10×35)=0.095 周期\text{AMAT}_{\text{TLB}} = 0 + 0.01 \times (6 + 0.10 \times 35) = 0.095 \text{ 周期}
  • 高压力场景(数据库随机访问):rL1=5%r_{\text{L1}} = 5\%rL2|L1=30%r_{\text{L2|L1}} = 30\%TPTW=120T_{\text{PTW}} = 120周期(PWC命中率降低)。
AMATTLB=0+0.05×(6+0.30×120)=0.05×42=2.1 周期\text{AMAT}_{\text{TLB}} = 0 + 0.05 \times (6 + 0.30 \times 120) = 0.05 \times 42 = 2.1 \text{ 周期}

Interpretation. 在低压力场景下,TLB的平均额外延迟不到0.1周期,几乎可以忽略——TLB设计良好地完成了"使地址翻译透明"的使命。但在高压力场景下,每次访存平均增加2.1周期的翻译延迟——对于一个4周期load-to-use延迟的处理器,这相当于将有效加载延迟增加了50%以上。

如果将大页纳入分析:使用2MB大页后,L1 dTLB的TLB Reach从72×4KB=288KB72 \times 4\text{KB} = 288\text{KB}扩展到72×2MB=144MB72 \times 2\text{MB} = 144\text{MB}rL1r_{\text{L1}}可能从5%降低到0.5%以下。此时AMATTLB=0.005×(6+0.30×120)=0.21\text{AMAT}_{\text{TLB}} = 0.005 \times (6 + 0.30 \times 120) = 0.21周期——降低了10倍。这就是大页对TLB敏感工作负载的巨大价值。

Verification. Google发布的数据中心工作负载分析("Profiling a Warehouse-Scale Computer", ISCA 2015)显示,启用大页后数据中心工作负载的平均TLB相关CPI从约0.15降低到约0.02,与我们的模型预测定性一致。

多页面大小的TLB设计

现代处理器必须同时支持多种页面大小——x86-64支持4KB、2MB和1GB,ARM AArch64支持4KB、16KB、64KB、2MB和1GB,RISC-V支持4KB、2MB和1GB。这给TLB设计带来了独特的挑战:不同页面大小的VPN匹配宽度不同,4KB页的VPN为36位(48位VA减去12位偏移),而2MB页的VPN仅为27位(48位VA减去21位偏移),1GB页更短为18位。TLB的CAM必须能够处理这种"可变长匹配"。

分区TLB(Partitioned TLB)

分区TLB为不同页面大小维护独立的物理CAM阵列,每个分区仅存储一种页面大小的条目。查找时,所有分区并行搜索,但每个分区使用固定宽度的VPN比较——4KB分区比较36位VPN,2MB分区比较27位VPN,1GB分区比较18位VPN。

分区TLB的核心优势在于CAM设计的简洁性。每个分区的比较宽度在设计时即可确定,无需运行时的动态掩码逻辑,时序路径干净。在第 3.0 章讨论的先进工艺节点(5nm及以下)中,CAM的时序裕量日益紧张,固定宽度比较意味着可以更激进地优化每个分区的物理布局。

AMD Zen 4的L1 dTLB就是典型的分区设计:72项用于4KB页(全相联),8项用于2MB页(全相联),8项用于1GB页(全相联),三个子阵列独立工作。Intel Golden Cove的L1 dTLB采用类似策略:64项4KB分区和32项2MB/1GB共享分区。

分区TLB的主要缺点是容量碎片化:如果工作负载几乎不使用大页(如传统的4KB页应用),2MB和1GB分区的容量被浪费;反之,如果工作负载全部使用2MB大页,4KB分区完全空闲。容量分配在设计时固定,无法根据运行时的工作负载特征动态调整。

统一TLB(Unified TLB)

统一TLB将所有页面大小的条目混合存储在同一个CAM阵列中。每个TLB条目携带一个页面大小标记字段(Page Size, PS),查找时根据PS字段动态确定VPN的比较范围。

为什么不用统一TLB?统一TLB的CAM需要实现"可变长匹配"——对于4KB页比较VPN[47:12]的全部36位,对于2MB页仅比较VPN[47:21]的27位。这要求CAM的比较逻辑支持掩码(mask)操作:根据页面大小字段生成掩码,屏蔽不参与比较的低位VPN。掩码逻辑增加了CAM匹配路径的延迟——额外的掩码与门至少增加一级逻辑深度,在5nm工艺下约为20–30ps。对于1周期命中的L1 TLB(时钟周期约200ps@5GHz),这20–30ps的额外延迟可能将L1 TLB的匹配推向时序极限,迫使设计者减小TLB容量或降低时钟频率。

此外,统一TLB的掩码比较还引入了一个微妙的正确性风险:如果掩码生成逻辑存在bug,不同页面大小的条目可能产生虚假命中(false hit)。例如,一个4KB页的条目本不应匹配一个2MB对齐的地址,但如果掩码错误地屏蔽了过多的低位,就可能出现错误匹配。这种bug极难在验证中捕获,因为它只在特定的页面大小混合场景下才会暴露。

混合方案

实际的处理器设计通常采用混合方案:L1 TLB使用分区设计(固定宽度CAM,简洁高效),L2 sTLB使用带掩码的统一设计(容量灵活,时序约束较宽松)。L2 sTLB的命中延迟为4–8周期,额外的掩码延迟在时序预算中可以轻松容纳。

图 11.4展示了分区TLB与统一TLB的架构对比。

分区TLB(左)与统一TLB(右)的架构对比
分区TLB(左)与统一TLB(右)的架构对比
::: tradeoff 设计权衡 2 — 分区TLB与统一TLB的设计权衡

分区TLB和统一TLB的选择取决于TLB在层次结构中的位置和时序预算:

  • L1 TLB(1周期时序预算):首选分区设计。CAM比较宽度固定,无需掩码逻辑,可以在极紧张的时序下实现高容量。Intel Golden Cove的96项L1 dTLB(64项4KB + 32项2MB/1GB)和AMD Zen 4的88项L1 dTLB(72项4KB + 8项2MB + 8项1GB)均采用此方案。

  • L2 sTLB(4–8周期时序预算):首选统一设计。时序宽松到足以容纳掩码延迟,统一存储可以根据工作负载动态分配不同页面大小的容量。Intel和AMD的L2 sTLB均为统一设计,支持4KB/2MB/1GB页的混合存储。

  • 例外:ARM Cortex-X4的L1 dTLB为48项全相联统一设计,支持4KB/16KB/64KB/2MB页的混合存储。这一选择与ARM支持更多页面大小有关——如果为4种页面大小各分一个分区,每个分区仅12项,冲突率可能过高。ARM通过优化CAM的掩码路径来控制时序。

:::

TLB Coalescing:硬件透明的大页合并

一个值得深入讨论的优化是TLB Coalescing(TLB合并)。即使操作系统没有显式配置大页,物理内存分配器通常也会分配物理上连续的页帧——例如Linux的伙伴系统(buddy allocator)倾向于分配2k2^k个连续的物理页。如果NN个连续的虚拟页恰好映射到NN个连续的物理页帧,且所有页面的权限和属性相同,那么这NN个TLB条目在逻辑上等价于一个覆盖N×page_sizeN \times \text{page\_size}的大页条目。

TLB Coalescing硬件在PTW填充TLB时,检测这种连续映射模式,并将多个小页条目合并为一个等效大页条目。例如,如果检测到512个连续的4KB页映射到512个连续的物理帧(且权限一致),就将它们合并为一个2MB的TLB条目。这使得TLB的有效容量倍增,而无需操作系统显式管理大页。

Intel从Skylake开始实现了TLB Coalescing(称为"Page Walk Coalescing"),AMD的Zen系列也实现了类似功能。实测数据表明,TLB Coalescing在未启用大页的工作负载中可以将L2 sTLB的有效命中率提升5–15%。

TLB Coalescing的实现需要注意以下细节:

  • 连续性检测:PTW在遍历页表时,可以同时读取多个PTE(一个Cache行包含8个PTE),检查它们的物理帧号和属性是否满足合并条件。

  • 部分合并:不要求严格的2N2^N对齐,可以实现任意范围的合并(例如将16个连续的4KB页合并为一个64KB的覆盖范围),但非2N2^N对齐的合并需要更复杂的掩码逻辑。

  • 一致性维护:当操作系统修改了合并范围内任何一个小页的映射或权限时,整个合并条目必须被失效。这意味着INVLPG对一个4KB页的失效可能需要失效一个覆盖2MB的合并条目,导致更大范围的TLB重建开销。

可变长TLB条目:Range TLB

学术界还提出了一种更激进的方案——Range TLB。Range TLB的每个条目不再限制为固定的页面大小(4KB、2MB、1GB),而是存储一个任意的虚拟地址范围[VAstart,VAend)[\text{VA}_{\text{start}}, \text{VA}_{\text{end}})到物理地址范围[PAstart,PAend)[\text{PA}_{\text{start}}, \text{PA}_{\text{end}})的映射。这使得TLB的每个条目可以覆盖任意大小的连续映射区域——一个条目就可以覆盖一个256MB的mmap区域。

Range TLB的CAM需要进行范围匹配(VAstartVAlookup<VAend\text{VA}_{\text{start}} \le \text{VA}_{\text{lookup}} < \text{VA}_{\text{end}})而非精确匹配,这比标准的CAM比较更复杂。但Range TLB的潜在收益巨大:一个64项的Range TLB在数据库等大连续映射的工作负载中,其有效TLB Reach可以达到传统TLB的100倍以上。目前Range TLB仍处于研究阶段,尚未在商用处理器中实现,但它代表了TLB设计的一个有前景的未来方向。

各级TLB的容量选择

表 11.4汇总了近年来主要处理器的TLB层次结构参数。

处理器L1 iTLBL1 dTLBL2 sTLBPTW
Intel Golden Cove256项, 8-way96项, FA2048项, 16-way硬件
(Alder Lake, 2021)(4KB+2MB+1GB)
AMD Zen 464项, FA72项, FA2048项, 8-way硬件
(Ryzen 7000, 2022)
AMD Zen 564项, FA96项, FA4096项, 8-way硬件
(Ryzen 9000, 2024)
Apple M3 Avalanche192项, FA160项, FA4096项, 12-way硬件
(2023)
ARM Cortex-X448项, FA48项, FA2048项, 8-way硬件
(2024)
RISC-V XiangShan48项, FA48项, FA1024项, 8-way硬件
Nanhu (2023)

主要处理器的TLB参数对比

从表表 11.4可以观察到以下趋势:

(1)L1 TLB容量稳步增长。早期处理器的L1 dTLB通常为32–48项,而最新的设计已经达到72–160项。Apple的M3 Avalanche核心以160项L1 dTLB领先于竞争对手,这与Apple处理器在大内存工作负载(如图像处理、视频编辑)上的定位一致。

(2)L2 sTLB容量从1024项向4096项演进。AMD Zen 5将L2 sTLB从Zen 4的2048项增加到4096项,Apple M3也达到4096项。更大的L2 sTLB对于云计算和数据库等具有大内存工作集的负载尤为重要。

(3)所有高性能处理器均采用硬件页表遍历器。在TLB完全缺失时,硬件PTW自动完成页表遍历,无需操作系统介入,将缺失处理延迟从数百周期的软件处理降低到20–50周期的硬件流水线延迟。

(4)Intel在iTLB上采用了独特的大容量组相联设计。256项的L1 iTLB是业界最大的,Intel通过采用8-way组相联(而非全相联)来控制面积和延迟,这一设计选择反映了Intel对服务器工作负载(如大型C++应用、数据库引擎)中iTLB缺失问题的重视。

(5)Apple的设计追求大容量与低延迟的极致平衡。Apple M3 Avalanche核心的L1 dTLB达到160项全相联——这在全相联设计中是极为激进的容量。160个并行比较器的面积和功耗开销相当可观,Apple可能使用了分阶段匹配或其他私有的CAM优化技术来控制延迟和功耗。Apple在L2 sTLB上也采用了4096项、12-way的高容量配置,12-way是一个不常见的路数选择,推测是Apple在8-way和16-way之间找到的最佳平衡点。

TLB的管理

TLB缺失的处理是TLB设计中最复杂的部分。当L1和L2 TLB均缺失时,需要遍历内存中的页表来获取地址映射。此外,当操作系统修改页表(如进程切换、内存映射变更、页面换出)时,需要使TLB中的相关表项失效(invalidate)。本节讨论这两方面的设计。

TLB缺失的处理

TLB缺失的处理有两种基本方法:硬件页表遍历(Hardware Page Table Walk)和软件TLB缺失处理(Software TLB Miss Handling)。

硬件页表遍历(Hardware PTW)

在硬件PTW模式下,处理器内部集成了专用的页表遍历硬件(Page Table Walker),当TLB缺失发生时,PTW硬件自动从页表的根指针(如x86的CR3、ARM的TTBR、RISC-V的satp寄存器)开始,逐级访问内存中的页表,最终获得物理地址映射。整个过程对软件透明——处理器自行完成遍历,仅在遇到页表项无效(Page Fault)时才触发异常交由操作系统处理。

硬件PTW的优势在于延迟低:页表遍历期间的内存访问可以利用Cache层次结构,如果页表项在L2 Cache中命中,每级遍历仅需10–15个周期。一个四级页表遍历的最佳情况延迟约为40–60个周期,最坏情况(所有级别的页表项都不在Cache中)约为200–600个周期。

硬件PTW的主要缺点是硬件复杂度较高,且与ISA定义的页表格式紧密耦合——如果ISA修改了页表格式,PTW硬件也需要相应修改。此外,硬件PTW难以支持不在ISA规范之内的自定义页表结构。

硬件PTW的复杂度还体现在对各种ISA特性的支持上。例如,x86-64的PTW需要处理:

  • 四级和五级页表的切换(由CR4.LA57位控制);

  • 大页检测——在遍历过程中,如果某级页表项的PS(Page Size)位为1,则该级即为叶子节点,遍历提前结束;

  • A/D位的硬件自动设置——PTW在成功遍历后需要将页表项的Accessed位置1(如果是写访问还需要将Dirty位置1),这涉及对页表项的原子读-修改-写操作;

  • 对PCID的处理——不同PCID对应不同的CR3值(页表根指针)。

软件TLB缺失处理

在软件模式下,TLB缺失触发一个异常(TLB miss exception),操作系统的异常处理程序负责遍历页表并将结果写入TLB。MIPS架构是最知名的采用软件TLB管理的ISA——MIPS的TLB是软件管理的,操作系统通过TLBWR(TLB Write Random)和TLBWI(TLB Write Indexed)等指令直接操作TLB表项。

软件TLB管理的优势在于灵活性:操作系统可以使用任意的页表格式(如倒排页表、多级页表、散列页表),甚至可以实现自定义的地址转换策略。缺点是延迟高——即使是最优化的软件TLB缺失处理程序也需要数十条指令,加上异常处理的开销(流水线刷新、上下文保存/恢复),总延迟通常在100–300个周期以上。

设计权衡 3 — 硬件PTW vs 软件TLB管理

在2030年代的处理器设计中,硬件PTW已经成为事实上的标准。x86、ARM、RISC-V三大主流ISA均定义了标准页表格式并要求或强烈建议使用硬件PTW。软件TLB管理主要存在于遗留架构(如MIPS)和某些特殊用途的嵌入式处理器中。 硬件PTW获胜的核心原因是:

  1. TLB缺失是频繁事件——典型工作负载中每千条指令发生0.5–5次L2 sTLB缺失,软件处理的高延迟不可接受。

  2. 硬件PTW可以与指令执行并行(特别是在乱序处理器中),而软件异常处理需要中断正常执行流。

  3. 现代页表格式已经标准化,页表结构的灵活性需求不再是关键因素。

硬件页表遍历器

硬件页表遍历器(Page Table Walker,PTW)是处理器MMU中最复杂的组件之一。PTW本质上是一个状态机,按照ISA定义的页表格式逐级访问内存中的页表项。以x86-64的四级页表为例,PTW需要依次访问PML4E、PDPTE、PDE和PTE四级页表项,如图图 11.5所示。

四级页表遍历器的状态机
四级页表遍历器的状态机

PTW与Cache的交互

这里出现了一个值得深思的层次关系:在第 10.0 章中,我们看到页表是存储在内存中的多级数据结构,而页表遍历的每一级访问本身也是一次内存访问——这次内存访问同样需要经过Cache层次结构。换言之,翻译地址的过程本身也需要访问Cache,形成了一种"翻译的翻译"的递归结构。但不同于TLB查找需要翻译的是虚拟地址,PTW的每级访问使用的是物理地址(页表项中存储的下一级页表基地址已经是物理地址),因此PTW的Cache访问不需要再经过TLB——这避免了无穷递归。

PTW的每级内存访问都可以利用处理器的Cache层次结构。页表本身是存储在物理内存中的数据结构,因此PTW发出的是物理地址访问——这些访问直接送入L1 D-Cache或L2 Cache的请求队列中。如果页表项在L2 Cache中命中(延迟约10–15个周期),四级遍历的总延迟约为40–60个周期;如果需要访问L3 Cache(延迟约30–50个周期),总延迟可达120–200个周期;如果需要访问主存(延迟约100–200个周期),总延迟将高达400–800个周期。

PTW的内存访问使用物理地址,因此不需要TLB转换——这避免了循环依赖的问题。但PTW的访问会占用Cache的带宽和端口,可能与正常的指令和数据访问产生冲突。在高TLB缺失率的工作负载中,PTW的Cache访问量不可忽视。

PTW的硬件实现

PTW的硬件实现通常包含以下组件:

  • 状态寄存器:保存当前遍历的状态(进行到第几级)、待翻译的虚拟地址、当前级的页表基地址等。

  • 请求生成逻辑:根据当前级的页表基地址和VPN的对应段,计算出下一级页表项的物理地址,并向Cache发出读请求。

  • 响应解析逻辑:当Cache返回页表项数据后,解析其中的有效位、权限位、大页标志和下一级基地址,决定遍历是否继续或提前终止。

  • A/D位更新逻辑:如果页表项的Accessed位未设置(或Dirty位未设置但当前是写访问),PTW需要通过Cache向内存发出写请求来更新这些位。这一操作必须是原子的(使用CAS或类似机制),以防止多核环境下的竞争条件。

  • TLB填充接口:遍历完成后,将结果写入L1和/或L2 TLB。

PTW的流水线化

在简单实现中,PTW按顺序执行每级页表的访问——等上一级的结果返回后才发出下一级的请求。这种串行设计的延迟等于各级访问延迟之和。在更高级的实现中,PTW可以流水线化:当一个TLB缺失的遍历正在进行时,PTW可以预先开始处理另一个TLB缺失的遍历(如果来源于不同的虚拟地址且它们的页表遍历路径不相互依赖)。这种流水线化的PTW可以在多个TLB缺失重叠发生时减少总的等待时间。

PTW的流水线化程度受限于Cache端口的数量。PTW的每级遍历都需要发出一次Cache读请求,如果L2 Cache仅有一个读端口(这在很多设计中是成立的),两个PTW实例不能在同一周期发出请求。实际的设计通常采用交织执行(interleaving):PTW A在等待第2级的Cache响应时,PTW B发出第1级的Cache请求。这样两个遍历可以重叠执行,总延迟接近单个遍历的延迟而非两倍。

为什么不实现4个或更多并行PTW?答案是收益递减加上复杂度增加。两个PTW已经可以覆盖大多数场景——在典型工作负载中,同时发生3个以上的TLB Miss的概率极低(需要3条独立的Load/Store指令同时Miss,且它们访问3个不同的虚拟页)。增加第3个PTW的面积和验证成本不值得其带来的微小性能收益。但在虚拟化环境下(嵌套遍历延迟10倍于裸机),3个以上的并发遍历变得更有价值——这也是Apple M系列据信支持4个以上并发PTW的原因之一。

虚拟化环境下的两级遍历

在虚拟化环境中,TLB缺失的处理变得更加复杂。虚拟机的Guest OS使用Guest Physical Address(GPA),GPA需要通过Hypervisor维护的EPT(Extended Page Table,Intel术语)或Stage-2页表(ARM术语)进一步转换为Host Physical Address(HPA)。这意味着一次Guest的页表遍历中的每一级访问本身都需要一次完整的Host地址转换。

对于x86-64的4级页表+4级EPT,最坏情况下一次地址转换需要4×4+4=204 \times 4 + 4 = 20次内存访问(Guest页表的每一级都可能触发4级EPT遍历,加上最终的数据访问的EPT遍历)。即使每次都在L2 Cache中命中(12周期/次),总延迟也高达240个周期。这使得在虚拟化环境中,TLB的命中率对性能的影响被极度放大——TLB缺失的代价是非虚拟化环境的4–5倍。

为了缓解这一问题,现代处理器在PTW中实现了EPT/Stage-2的缓存。Intel的PTW可以缓存EPT的中间页表项,AMD的NPT(Nested Page Table)同样有专门的缓存。这些缓存可以将20次内存访问减少到4–6次,显著降低虚拟化环境下的地址转换开销。

页表遍历缓存

四级页表遍历需要四次内存访问,即使每次都在L2 Cache中命中,总延迟仍然高达40–60个周期。一个关键的优化是引入页表遍历缓存(Page Walk Cache,PWC),也称为MMU Cache或Translation Walk Cache。PWC缓存的不是最终的VPN→PPN映射(那是TLB的工作),而是中间级页表项——即页表遍历过程中非叶子节点的页表项。

PWC的工作原理

以x86-64的四级页表为例,一次完整遍历需要依次读取PML4E→PDPTE→PDE→PTE。其中PML4E是最高级的页表项,一个PML4E覆盖512GB的虚拟地址空间。如果PWC缓存了某个PML4E,则后续所有落在该512GB范围内的TLB缺失在遍历时可以跳过第一级,直接从第二级开始。类似地:

  • 缓存一个PDPTE可以跳过前两级遍历(该PDPTE覆盖1GB地址范围);

  • 缓存一个PDE可以跳过前三级遍历(该PDE覆盖2MB地址范围)。

PWC的命中可以将四级遍历缩减为三级、两级甚至一级遍历,显著减少页表遍历的延迟。

性能分析 3 — PWC对页表遍历延迟的影响

假设每级页表访问在L2 Cache中命中时延迟为12个周期。无PWC时,四级遍历需要4×12=484 \times 12 = 48个周期。如果PWC缓存了PDE(覆盖2MB范围),则遍历从PTE级开始,仅需1×12=121 \times 12 = 12个周期——延迟降低了75%。

在典型的服务器工作负载中,PWC的命中率分布大致为:

  • PML4E级PWC命中率 > 99%(512GB粒度,几乎总是命中);

  • PDPTE级PWC命中率约95–98%(1GB粒度);

  • PDE级PWC命中率约80–95%(2MB粒度)。

因此,大多数页表遍历实际上只需要1–2次内存访问,平均延迟约为15–25个周期而非48个周期。

PWC的实现通常是一组小容量的全相联或低路数组相联缓存,每级页表各一个。例如Intel Skylake以来的处理器包含:

  • PML4 Cache:4项(仅需缓存极少量的PML4E,因为每个PML4E覆盖512GB);

  • PDP Cache:32项;

  • PD Cache:32项。

AMD的处理器同样实现了类似的PWC结构。ARM架构称之为"Translation Walk Cache"或"Intermediate PA Cache"。RISC-V的开源处理器(如香山处理器)也实现了多级PWC来加速Sv39/Sv48页表遍历。

硬件描述 2 — PWC的面积效率分析

PWC是处理器中面积效率最高的缓存结构之一。以Intel的PML4 Cache为例:仅4项,每项约80位(64位物理地址 + 控制位),总存储仅4×80=3204 \times 80 = 320位(40字节)。但每个PML4 Cache条目覆盖512GB的虚拟地址空间——在绝大多数程序中,整个进程的虚拟地址空间都落在1–2个PML4条目覆盖的范围内。这意味着4项PML4 Cache的命中率接近100%,每次命中节省一级页表遍历(约12周期)。

这40字节的硬件投入,在每次TLB Miss时节省约12周期——假设每千条指令有0.5次TLB Miss(L2 sTLB也Miss),PWC节省的周期为0.5/1000×12=0.0060.5/1000 \times 12 = 0.006个CPI。对于一个IPC=3.0的处理器,这对应约2%的性能提升。40字节的CAM/SRAM在5nm工艺下占用约0.001mm20.001\,\mathrm{mm}^2的面积。相比之下,增加256项L2 sTLB(从2048到2304项)可能只带来0.5%的性能提升,但面积开销约为0.01mm20.01\,\mathrm{mm}^2。PWC的面积效率比增加TLB容量高约40倍。

这解释了为什么所有现代高性能处理器都实现了PWC——它是少数几个"几乎无成本"的性能优化之一。

PWC的替换策略和一致性

PWC的替换策略通常采用伪LRU——由于PWC容量极小(每级通常4–32项),替换策略对性能的影响小于TLB。但PWC的一致性维护需要特别关注:当操作系统修改页表结构时(例如将一个页目录项指向新的页表页),PWC中缓存的旧页目录项必须被失效。

x86的INVLPG指令不仅失效TLB中的表项,还会失效PWC中的对应条目。ARM的TLBI指令同样如此。如果PWC的失效不及时,PTW可能使用过时的中间页表项来遍历页表,最终获得错误的物理地址——这是一个极其严重的正确性问题。

在实现层面,PWC的失效通常比TLB更保守:许多处理器在执行TLB失效指令时会同时刷新整个PWC,即使只有特定的表项需要失效。这种保守策略牺牲了一些性能(PWC需要重新填充),但大大简化了正确性验证。

PWC在5级页表下的重要性

随着x86-64和RISC-V引入5级页表(LA57/Sv57),页表遍历深度从4级增加到5级。如果没有PWC,5级遍历的延迟将从4级的48周期增加到5×12=605 \times 12 = 60周期——增加25%。但有了PWC,5级遍历的实际延迟增加很小,因为新增的第5级(PML5E/Root Page Table)覆盖范围极大(128PB),PWC几乎总是能命中。因此,PWC的存在是支撑5级页表可行性的关键硬件基础。

并行页表遍历

在乱序超标量处理器中,流水线中可能同时存在多条未完成的加载和存储指令,如果这些指令的地址映射不在TLB中,就会产生多个并发的TLB缺失。如果只有一个PTW,这些缺失必须串行处理,后续缺失被阻塞,导致流水线暂停时间倍增。

为解决这一问题,现代高性能处理器配备了多个并行的PTW,或者至少配备了一个PTW加上一个缺失等待队列(miss queue),允许多个TLB缺失同时被处理。

多PTW设计

Intel从Skylake开始配备了2个并行的PTW,Golden Cove和后续微架构沿用了这一设计。AMD Zen 4/Zen 5同样支持2个并行的PTW。Apple的高性能核心甚至可能支持更多的并发页表遍历。

多PTW设计的关键挑战包括:

  1. Cache端口竞争:多个PTW同时发出内存访问请求,与正常的指令/数据Cache访问竞争L2 Cache的端口。需要仲裁逻辑来调度这些请求。

  2. 一致性:如果两个PTW同时遍历的页表项被操作系统修改(例如页面被换出),需要确保两个PTW都能正确检测到这一变化。通常通过在TLB填充时再次检查页表项的有效性来实现。

  3. 合并相同的遍历:如果两个TLB缺失指向同一个虚拟页(例如两条连续的加载指令访问同一页的不同偏移),它们的页表遍历是相同的,应该合并为一次遍历。PTW需要实现缺失合并(miss coalescing)逻辑来检测这种情况。

TLB缺失等待队列

即使只有一个PTW,也可以通过缺失等待队列(TLB Miss Queue或Page Walk Queue)来支持一定程度的并发。当PTW正在处理一个缺失时,新的TLB缺失被放入等待队列。PTW按顺序从队列中取出缺失进行处理。等待队列还可以实现缺失合并——如果队列中已有相同VPN的缺失,新的缺失直接与之合并,无需重复遍历。

典型的TLB缺失等待队列容量为4–16项。如果队列满且PTW仍在忙碌,后续的TLB缺失将导致流水线暂停——这是TLB性能的最终瓶颈之一。

硬件描述 3 — 香山处理器的TLB与PTW设计

香山(XiangShan)是中国科学院计算技术研究所开发的开源RISC-V高性能处理器。其南湖(Nanhu)微架构的TLB和PTW设计如下:

  • L1 iTLB:48项全相联,支持Sv39/Sv48的4KB/2MB/1GB页;

  • L1 dTLB:48项全相联,支持相同页大小;

  • L2 sTLB:1024项,8-way组相联;

  • PTW:支持2个并行的页表遍历请求;

  • PWC:实现了3级页表遍历缓存(对应Sv39的3级页表)。

香山处理器的PTW采用了一种称为"Repeater"的设计模式:PTW的每一级遍历都通过L1 D-Cache发出内存访问请求,利用D-Cache的已有端口而非独立的内存接口。这种设计减少了硬件复杂度,但PTW的请求会与正常的加载/存储指令竞争D-Cache端口。为此,香山实现了PTW请求的优先级调度——PTW请求的优先级高于预取请求但低于需求加载请求,在Cache端口空闲时优先处理PTW请求。

香山处理器的TLB和PTW设计代码完全开源(基于Chisel HDL),是学习现代TLB设计实现细节的宝贵资源。

PCID/ASID与TLB刷新优化

当操作系统进行进程切换时,新进程的虚拟地址映射与旧进程不同。如果TLB中仍然保留着旧进程的映射,新进程可能会错误地使用旧进程的物理地址,造成安全漏洞和功能错误。因此,传统做法是在每次进程切换时刷新整个TLB(flush all TLB entries)。

全局TLB刷新的代价

TLB刷新的代价极其高昂。以一个2048项L2 sTLB为例,刷新后所有表项失效,后续的每次地址转换都会缺失,直到TLB被重新填充。假设工作负载的指令混合中每5条指令有一条访存指令,每次TLB缺失的平均惩罚为30个周期(L2 sTLB缺失、PTW在L2 Cache中命中),则刷新后重新填充2048项TLB需要约2048×30=614402048 \times 30 = 61440个周期——在4GHz处理器上约为15微秒。在高频进程切换的场景中(如Linux的默认调度间隔为4ms,但在高负载下可能低至1ms),这一开销占比可达1.5%以上。

ASID/PCID避免全局刷新

ASID(Address Space Identifier)或PCID(Process-Context Identifier,x86的术语)为每个进程分配一个唯一的标识符。TLB表项在存储时同时记录对应进程的ASID,查找时同时比较VPN和ASID。这样,不同进程的TLB表项可以共存于同一个TLB中,进程切换时只需更新当前的ASID值而不需要刷新TLB。

Intel从Westmere微架构开始支持PCID,但直到Haswell之后才在Linux内核中被默认启用。PCID的使能使得进程切换时的TLB刷新开销几乎被消除——只有当所有12位PCID被分配完(4096个进程)时,才需要回收旧PCID并刷新其对应的TLB表项。

选择性TLB失效

除了进程切换,操作系统在修改页表时也需要使对应的TLB表项失效。例如,当一个页被解除映射(munmap系统调用)时,操作系统需要确保所有处理器核心的TLB中不再保留该页的映射。

x86提供了INVLPG指令来失效单个TLB表项(按VPN指定),避免了全局刷新。ARM提供了按ASID、按地址、按全局等多种粒度的TLB失效指令(TLBI指令族)。RISC-V的SFENCE.VMA指令支持按地址和按ASID的选择性失效。

在多核系统中,TLB失效还需要广播到其他核心——这称为TLB Shootdown。传统的TLB Shootdown通过处理器间中断(IPI)实现:发起核心向所有可能缓存了该映射的核心发送IPI,接收核心在中断处理程序中执行INVLPG。这一过程开销巨大,在96核甚至更多核的服务器上可能需要数十微秒。

Intel的INVLPGB(Invalidate Page Global Broadcast,Zen 3+)和ARM的TLBI广播机制试图通过硬件广播来减少TLB Shootdown的软件开销。INVLPGB允许一个核心通过硬件互联直接向其他核心的TLB发送失效请求,无需IPI中断。

TLB Shootdown的性能影响

TLB Shootdown在大规模多核系统中可能成为严重的性能瓶颈。考虑一个128核的服务器处理器:当一个核心执行munmap释放一段被多个线程共享的内存区域时,操作系统需要向所有127个其他核心发送IPI请求TLB失效。每个核心收到IPI后需要:

  1. 保存当前执行上下文(约10–20周期);

  2. 跳转到IPI处理程序(约20–30周期);

  3. 执行INVLPG指令(约10–50周期,取决于实现和需要失效的页数);

  4. 恢复执行上下文(约10–20周期)。

整个过程在每个核心上的开销约为50–120周期,但关键问题在于同步等待:发起核心必须等待所有目标核心确认已完成TLB失效后才能继续。在128核系统中,即使每个核心的处理只需100周期(25ns@4GHz),由于IPI的分发和确认通过互联网络传递(延迟约50–200ns),总的TLB Shootdown延迟可达1–10微秒。

AMD的INVLPGB指令通过硬件广播机制将这一开销降低了一个数量级——硬件互联可以在约100ns内将失效请求广播到所有核心,无需IPI中断,也无需每个核心执行软件处理程序。

案例研究 2 — Linux内核的PCID优化

Linux内核从4.15版本开始全面启用PCID支持(由Andy Lutomirski贡献)。在PCID启用之前,每次用户态到内核态的切换(syscall)都需要刷新TLB(因为Meltdown漏洞的KPTI缓解措施需要切换页表)。启用PCID后,内核态和用户态各自使用不同的PCID,切换页表时不再需要刷新TLB。

实测数据表明,在频繁系统调用的工作负载中,PCID的启用可以将系统调用的开销降低40–60%。例如在pipe-based IPC benchmark中,启用PCID后吞吐量提升了约50%。这一优化对于数据库服务器(MySQL、PostgreSQL等频繁执行syscall的应用)尤为重要。

TLB Shootdown的核间同步

当一个核心上的操作系统修改页表——例如通过munmap解除内存映射、通过mprotect变更页面权限、或者将页面换出到磁盘——必须确保所有核心的TLB中不再保留过时的旧条目。否则,其他核心可能使用旧的TLB映射访问已经被重新分配的物理页,导致数据损坏或安全漏洞。这种跨核心的TLB失效同步操作称为TLB Shootdown

TLB Shootdown是多核处理器中最昂贵的同步操作之一。在第 9.0 章讨论的Cache一致性协议中,硬件通过Snoop或Directory自动维护Cache行的一致性,软件无需介入。但TLB没有类似的硬件一致性协议——TLB条目的一致性完全由操作系统软件负责维护。

为什么TLB不像Cache那样使用硬件一致性协议?这是一个值得深入思考的"为什么不用X"问题。理论上,可以设计一种"TLB一致性协议":当一个核心修改页表时,硬件自动检测到页表的写入(类似于Cache的Snoop),并广播失效消息到所有核心的TLB。但这种设计面临几个根本性困难:(1)页表是普通内存中的数据结构,硬件无法区分"对页表的写入"和"对普通数据的写入"——需要标记所有页表页为"TLB一致性受管页",维护开销巨大;(2)一次页表修改可能影响TLB中的多个条目(例如修改一个PDE会影响所有使用该PDE的512个PTE映射),一致性协议需要理解页表的多级结构;(3)页表修改是由操作系统通过显式的系统调用触发的,频率远低于Cache写入(每秒数千到数万次vs每秒数十亿次),软件处理的摊销成本在历史上被认为是可以接受的。

然而,随着核心数增长到128甚至更多,纯软件的IPI方案的可扩展性越来越差。AMD的INVLPGB指令和ARM的TLBI广播代表了一种中间路线——不是完全的硬件TLB一致性,而是为软件提供高效的硬件辅助失效广播机制。这种"软件决策 + 硬件执行"的模式可能是2030年代TLB一致性管理的主流方向。

x86的IPI方案

在x86架构上,TLB Shootdown通过IPI(Inter-Processor Interrupt)实现。当核心A修改了页表并需要使其他核心的TLB失效时,流程如下:

  1. 核心A完成页表修改,执行INVLPG指令使本地TLB失效。

  2. 核心A通过APIC(Advanced Programmable Interrupt Controller)向所有可能缓存了被修改映射的核心发送IPI。

  3. 每个接收到IPI的核心中断当前执行,保存上下文,跳转到IPI处理程序。

  4. IPI处理程序执行INVLPG指令(或INVPCID指令按PCID选择性失效),使本地TLB中对应的条目失效。

  5. 每个核心通过内存中的标志位或计数器向核心A确认失效完成。

  6. 核心A等待所有目标核心的确认,然后才能继续执行(例如释放物理页帧或重新映射)。

这个过程中的关键瓶颈是同步等待。核心A必须等到最后一个目标核心确认失效完成后才能继续——这意味着总延迟取决于最慢的核心。在128核的服务器处理器上,即使每个核心的中断处理仅需100周期(约25ns@4GHz),IPI通过片上互联网络传播的延迟约50–200ns,加上所有核心的确认汇聚延迟,总的TLB Shootdown延迟可达1–10μ\mus。

ARM的广播TLBI方案

ARM架构采用了与x86不同的硬件辅助方案。ARM的TLBI(TLB Invalidate)指令族支持内循环共享域广播(Inner Shareable domain broadcast):当一个核心执行如TLBI VALE1IS指令时,硬件自动将失效请求广播到同一Inner Shareable域内的所有核心的TLB。核心无需发送软件IPI,整个过程由硬件互联完成。

广播完成后,核心需要执行DSB ISH(Data Synchronization Barrier, Inner Shareable)指令来等待所有核心确认TLB失效已完成。DSB之后再执行ISB(Instruction Synchronization Barrier)来确保后续指令使用更新后的TLB映射。

ARM方案的优势在于减少了软件开销——无需IPI中断处理程序,无需保存/恢复上下文,目标核心的TLB失效由硬件在后台完成,不中断正在执行的指令。缺点是硬件互联需要支持TLBI广播协议,增加了片上互联的复杂度。

x86 IPI方案与ARM硬件TLBI广播方案的对比
x86 IPI方案与ARM硬件TLBI广播方案的对比

Shootdown延迟的定量分析

为了量化TLB Shootdown的代价,我们建立一个简单的延迟模型。设系统有NN个核心,Shootdown发起核心需要使N1N-1个目标核心的TLB失效。总的Shootdown延迟TsdT_{\text{sd}}包含以下组成部分:

Tsd=Tsend+Tpropagate+Tprocess+Tack T_{\text{sd}} = T_{\text{send}} + T_{\text{propagate}} + T_{\text{process}} + T_{\text{ack}}

其中:

  • TsendT_{\text{send}}:发送IPI请求的时间。如果APIC支持广播模式,Tsend=O(1)T_{\text{send}} = O(1);如果必须逐核发送,Tsend=O(N)T_{\text{send}} = O(N)

  • TpropagateT_{\text{propagate}}:IPI通过片上互联传播到最远核心的延迟。取决于互联拓扑(环形、Mesh、树形),典型值为50–200ns。

  • TprocessT_{\text{process}}:每个核心执行INVLPG的时间。包括中断入口(约30周期)、INVLPG执行(约20周期)、中断出口(约30周期),共约80周期(20ns@4GHz)。

  • TackT_{\text{ack}}:最慢核心的确认到达发起核心的时间。与TpropagateT_{\text{propagate}}类似。

在一个128核的Mesh互联服务器处理器上(如Intel Sapphire Rapids),假设Mesh直径为16跳,每跳延迟约5ns:

Tsd50ns(send)+80ns(propagate)+20ns(process)+80ns(ack)=230nsT_{\text{sd}} \approx 50\,\text{ns}(\text{send}) + 80\,\text{ns}(\text{propagate}) + 20\,\text{ns}(\text{process}) + 80\,\text{ns}(\text{ack}) = 230\,\text{ns}

但这是理想情况。在实际系统中,如果某个目标核心正在执行不可中断的操作(如另一个IPI处理、自旋锁临界区),TprocessT_{\text{process}}可能增加到数微秒。因此实测的Shootdown延迟分布通常呈长尾分布——中位数约200–500ns,但P99可达5–10μ\mus。

大规模多核下的Shootdown瓶颈

随着核心数的增长(64核、128核、甚至更多),TLB Shootdown的可扩展性成为严重问题。主要瓶颈包括:

  • IPI风暴:在x86方案中,一次Shootdown需要向N1N-1个核心发送IPI。APIC的IPI发送速率有限(每次IPI写入APIC的ICR寄存器需要数十周期),NN较大时IPI发送本身就需要显著的时间。

  • 中断处理开销:每个核心收到IPI后都需要执行完整的中断入口/出口序列(保存寄存器、跳转处理、恢复寄存器),每个核心约50–120周期。在128核系统上,所有核心的累计中断处理开销可达数万个核周期。

  • 同步点串行化:发起核心必须等待最慢的核心完成失效。如果某个核心正好在执行不可中断的操作(如另一个IPI处理、NMI处理),等待时间可能非常长。

  • 互联带宽:ARM的广播方案虽然避免了软件开销,但TLBI广播消息需要通过片上互联传播到所有核心,在大规模多核上可能造成互联拥塞。

Shootdown优化技术

为了减轻TLB Shootdown的性能影响,硬件和软件都发展出了多种优化技术:

  1. 延迟Shootdown(Lazy Shootdown)。不立即发送IPI使其他核心的TLB失效,而是标记被修改的页表项,等到其他核心下次访问该页面时才触发失效。在很多情况下,被解除映射的页面在短期内不会被其他核心访问,延迟Shootdown可以完全避免IPI开销。但这种方案在安全关键场景(如内存隔离)中不适用——必须立即确保所有核心的TLB一致。

  2. 批量Shootdown。将多个页面的失效请求合并为一次Shootdown操作。例如,munmap释放一个大的地址区间(包含数百个页面)时,与其为每个页面发送一次IPI,不如收集所有待失效的地址,一次性发送IPI并在IPI处理程序中批量执行INVLPG。Linux内核正是这样做的——flush_tlb_mm_range函数将待失效的地址范围打包到一个结构体中,然后一次IPI完成所有失效。当待失效的页面数量超过一个阈值时,内核甚至会选择刷新整个TLB而非逐页失效。

  3. PCID避免不必要的Shootdown。如果修改的页表属于一个特定进程(由PCID标识),且该进程当前没有在其他核心上运行,则无需发送IPI——下次该进程在其他核心上被调度时,操作系统可以检查该PCID的TLB是否需要失效。Linux内核通过维护每个核心的"TLB generation"计数器来实现这一优化。

  4. AMD INVLPGB指令。AMD从Zen 3开始引入INVLPGB(Invalidate Page Global Broadcast)指令,通过硬件互联将TLB失效请求广播到所有核心,无需软件IPI。INVLPGB后跟TLBSYNC指令等待广播完成。这将x86的Shootdown延迟从微秒级降低到百纳秒级,接近ARM的TLBI广播方案。

案例研究 3 — Linux内核的TLB Shootdown优化

Linux内核(5.x及以上版本)对TLB Shootdown实现了多层优化:

  • 跟踪PCID活跃集:内核在每个核心上维护一个位图(mm_cpumask),记录哪些核心当前运行着使用同一地址空间的线程。Shootdown时仅向位图中标记的核心发送IPI,避免向无关核心发送不必要的中断。

  • 阈值切换:当待失效的页面数量超过一个可配置的阈值(默认为33个页面)时,内核放弃逐页失效,转而使用全局INVPCID按PCID刷新该进程在所有核心上的TLB条目。全局刷新虽然丢弃了更多TLB条目,但避免了大量逐页INVLPG的开销。

  • KPTI与PCID的协同:启用KPTI(Kernel Page Table Isolation)后,用户态和内核态使用不同的页表(不同的PCID)。进入内核态时切换到内核PCID,此时用户态TLB条目自动不可见(PCID不匹配),无需刷新。返回用户态时切换回用户PCID,内核TLB条目同样不可见。这一设计避免了每次syscall的TLB刷新,使KPTI的性能开销从最初的30%降低到约2–5%。

设计提示

TLB Shootdown是大规模多核处理器设计中一个常被低估的可扩展性瓶颈。处理器架构师在评估核心数扩展(scaling)时,通常关注Cache一致性协议的可扩展性(如Directory vs Snoop),但TLB Shootdown的可扩展性同样关键。一个经验法则:如果目标工作负载中munmapmprotect的频率超过每秒10万次(在微服务和容器化部署中很常见),TLB Shootdown的开销在64核以上的系统中可能超过Cache一致性的开销。硬件层面的缓解措施包括:(1)支持范围失效(range invalidation)而非逐页失效,减少失效命令的数量;(2)实现类似AMD INVLPGB的硬件广播机制;(3)在片上互联中为TLBI请求预留专用通道,避免与一致性流量竞争。

TLB预取

正如第 5.0 章中数据Cache预取可以提前将数据从低层存储搬运到高层存储以隐藏访存延迟一样,TLB预取可以提前将地址翻译结果填充到TLB中,避免在关键路径上产生TLB Miss。TLB预取是对"地址翻译也是一种数据访问"这一洞察的直接应用——页表本身就是存储在内存中的数据结构,对页表的访问同样可以被预取优化。

基于页表遍历结果的TLB预取

当硬件PTW完成一次页表遍历后,不仅可以将最终的VPN\toPPN映射填充到TLB,还可以"顺便"将遍历路径上其他有效的页表项也填充到TLB中。例如,PTW在遍历到PDE级别时,可以读取当前PDE指向的整个PT页(包含512个PTE),将其中有效的PTE全部预填充到L2 sTLB中。这种PT页预取(Page Table Page Prefetch)的代价很小(一个4KB的PT页在一次Cache行读取中即可获得8个PTE),但收益显著——后续访问同一2MB区域内的其他虚拟页时可以直接在TLB中命中。

基于访问模式的TLB预取

处理器可以检测地址翻译的访问模式,并预测即将需要的地址翻译。例如:

  • 顺序页预取:当连续的TLB Miss命中页号NNN+1N+1N+2N+2时,预取器可以提前为页号N+3N+3N+4N+4等触发PTW,填充TLB。这对于数组遍历和顺序文件读取等工作负载效果显著。

  • 步长检测:类似于数据Cache预取中的步长预取器,TLB预取器可以检测固定步长的页面访问模式(如每隔kk个页面访问一次),预测下一个需要翻译的页面。

  • 预取驱动的TLB填充:当数据Cache的硬件预取器发出预取请求时,如果预取目标地址不在TLB中,可以提前触发PTW来填充TLB表项。这种"预取驱动的TLB预填充"将数据预取和地址翻译预取统一起来。Intel的某些微架构实现了此机制。

MOT:并发页表遍历

MOT(Multiple Outstanding Translations)是指处理器同时维护多个未完成的页表遍历请求的能力。在传统的单PTW设计中,TLB Miss是串行处理的——一个遍历完成后才能开始下一个。但在乱序处理器中,多条指令可能同时引发TLB Miss,如果串行处理,后续指令将被阻塞。

MOT通过以下机制实现并发遍历:

  • 多个物理PTW实例:Intel Skylake以来的处理器支持2个并行PTW,AMD Zen 4/5同样支持2个。每个PTW实例是独立的状态机,可以同时处理不同VPN的遍历。

  • PTW流水线化:单个PTW可以在等待一级页表项从Cache返回的间隙,开始处理另一个遍历请求的第一级。这种交织执行(interleaving)提高了PTW的吞吐量。

  • Miss Queue:当所有PTW实例都忙碌时,新的TLB Miss请求被放入等待队列(Miss Queue),PTW空闲时按序处理。队列还提供Miss合并功能——如果队列中已有相同VPN的请求,新请求直接合并。

在虚拟化环境下(如第 12.0 章将详细讨论),嵌套页表遍历的延迟可达1000+周期,MOT的价值被进一步放大——没有MOT,一个嵌套遍历将阻塞所有后续的TLB Miss处理长达数百纳秒。

性能分析 4 — MOT对虚拟化环境TLB性能的影响

Setup. 比较单PTW与双PTW在虚拟化环境下处理连续TLB Miss的性能差异。假设嵌套页表遍历延迟为800周期,每千条指令产生2次L2 sTLB Miss。

Strategy. 计算单PTW和双PTW配置下,TLB Miss处理的吞吐量和对IPC的影响。

Derivation. 单PTW配置:PTW每800周期处理一个Miss,吞吐量为1/800=0.001251/800 = 0.00125次/周期。如果Miss到达率为2/(1000/IPC)2/(1000/\text{IPC})次/周期(假设IPC=2,则到达率为2/500=0.0042/500 = 0.004次/周期),PTW的利用率为0.004/0.00125=3.20.004/0.00125 = 3.2——严重超载。这意味着PTW Miss Queue会持续积压,导致流水线频繁暂停。

双PTW配置:吞吐量翻倍为2/800=0.00252/800 = 0.0025次/周期,利用率降至0.004/0.0025=1.60.004/0.0025 = 1.6——仍然偏高,但通过PTW流水线化(交织处理不同遍历的不同级别)和PWC缓存(将有效遍历深度从24步降到6–8步,实际延迟从800周期降到200周期),双PTW可以将利用率控制在合理范围。

Interpretation. 在虚拟化环境下,单PTW几乎不可行——嵌套遍历的超长延迟使PTW成为性能瓶颈。双PTW配合PWC是最低要求。Apple M系列的高性能核心据信支持4个以上的并发页表遍历,这在运行多个虚拟机的macOS环境中尤为重要。

Verification. AMD的技术文档显示Zen 4的双PTW在虚拟化工作负载中将地址翻译相关的流水线暂停降低了约35%(相比单PTW的理论值),与我们的定性分析一致。

设计提示

TLB预取与数据预取有一个重要的协同效应:如果数据预取器检测到一个跨越页边界的访问模式(如步长为8KB的数组遍历),它不仅应该预取数据到Cache,还应该触发TLB预填充——否则预取的数据到达Cache后,实际的Load指令仍然会因为TLB Miss而暂停。Intel和AMD的现代微架构都实现了这种"预取驱动的TLB预填充"(prefetch-triggered TLB prefill),将数据预取和TLB预取统一为一个动作。设计者在实现硬件预取器时,应该始终考虑预取目标是否可能导致TLB Miss。

TLB与Cache的配合

TLB和Cache是处理器访存路径上的两个关键组件:TLB将虚拟地址转换为物理地址,Cache存储最近使用的数据和指令。两者的配合方式深刻影响着访存延迟和处理器流水线的设计。核心问题是:Cache的索引和标记是使用虚拟地址还是物理地址?这个选择产生了四种可能的组合,其中两种(PIPT和VIPT)在实际处理器中最为常见。在第 5.0 章中我们从Cache设计的角度讨论了Cache组织方式——本节聚焦于TLB的视角,分析TLB如何与各种Cache组织方式协同工作。

在讨论具体方案之前,先明确四种可能的组合及其特点。表表 11.5总结了四种Cache组织方式。

方式索引标记别名问题现代处理器中的应用
VIVT虚拟虚拟严重几乎不用(早期ARM)
VIPT虚拟物理可控L1 I/D-Cache(主流)
PIPT物理物理L2/L3 Cache,部分L1
PIVT物理虚拟严重无实际应用

Cache的四种地址组织方式

VIVT(Virtually Indexed, Virtually Tagged)Cache使用虚拟地址进行索引和标记,完全不需要TLB参与Cache查找,延迟最低。但VIVT存在严重的别名问题和同音异义问题(不同进程的相同虚拟地址映射到不同物理地址),且每次进程切换都需要刷新Cache(除非使用ASID标记Cache表项)。VIVT仅在早期ARM处理器(如ARM926)中使用,现代设计已经完全抛弃。PIVT在理论上存在但没有实际价值——它结合了PIPT的高延迟和VIVT的别名问题,是所有组合中最差的。

PIPT Cache

PIPT(Physically Indexed, Physically Tagged)Cache使用物理地址进行索引和标记比较。这是概念上最简单的方案:首先通过TLB将虚拟地址转换为物理地址,然后用物理地址访问Cache。

PIPT的优点

PIPT不存在任何别名(aliasing)问题。由于Cache使用物理地址索引和标记,相同的物理位置在Cache中只会有一个副本,无论有多少个虚拟地址映射到该物理地址。这使得Cache的一致性维护非常简单,也避免了多进程共享内存时的别名困扰。

PIPT的缺点

PIPT的致命缺点是TLB查找位于Cache访问的关键路径上。Cache访问必须等待TLB完成地址转换后才能开始,两者串行执行。假设L1 TLB查找需要1个周期,L1 D-Cache查找需要3个周期,则加载指令的总延迟至少为1+3=41 + 3 = 4个周期(加上地址生成的1个周期,共5个周期)。

在追求极低加载延迟的高性能处理器中,PIPT的这一额外周期延迟是不可接受的。因此,现代高性能处理器的L1 D-Cache几乎不使用纯PIPT,而是采用VIPT方案来将TLB查找和Cache查找并行化。

PIPT主要用于L2及更高级别的Cache。L2 Cache的访问延迟本身就较高(10–15个周期),额外的TLB查找延迟可以被流水线吸收,而PIPT的无别名优势简化了Cache管理。

需要注意的是,L2 Cache虽然使用物理地址,但它不需要自己的TLB——L1 TLB的转换结果在L1 Cache缺失后仍然可用(通过MSHR中保存的物理地址),直接传递给L2 Cache使用。因此L2 Cache的PIPT设计不会引入额外的TLB查找延迟。

某些嵌入式处理器在L1 D-Cache上也使用PIPT,以换取设计的简洁性。例如ARM Cortex-A55(小核)的L1 D-Cache为64KB PIPT。由于A55的目标是面积和功耗最小化而非极致性能,PIPT带来的额外延迟在其设计权衡中是可以接受的。

VIPT Cache

VIPT(Virtually Indexed, Physically Tagged)Cache使用虚拟地址的低位进行索引选择Cache组,同时使用物理地址的高位(经TLB转换后)进行标记比较。这种方案的关键洞察是:Cache索引只需要地址的低位,而在常见的配置下,这些低位在虚拟地址和物理地址中是相同的(页内偏移部分),因此索引可以在TLB查找开始前就确定,从而实现TLB和Cache的并行查找。

图 11.7展示了VIPT Cache的工作原理。

VIPT Cache:TLB查找与Cache索引并行执行
VIPT Cache:TLB查找与Cache索引并行执行

VIPT并行的条件——一个第一性原理推导

VIPT能够正确工作且无别名的前提可以从第一性原理推导。页式虚拟存储器的一个关键性质是:虚拟地址中的页内偏移(offset)部分在翻译后保持不变——如果虚拟地址VA=VPNP+offset\text{VA} = \text{VPN} \cdot P + \text{offset}(其中PP是页大小),对应的物理地址PA=PPNP+offset\text{PA} = \text{PPN} \cdot P + \text{offset},两者的低log2P\log_2 P位完全相同。

这一性质意味着:如果Cache的索引位全部来自这个不变的偏移部分,那么虚拟地址索引和物理地址索引必然相同——VIPT在功能上等价于PIPT,无任何别名。具体条件为:Cache索引使用的地址位必须完全落在页内偏移范围内。换言之,Cache索引位不能越过页边界——否则虚拟地址的索引位和物理地址的索引位可能不同,导致别名问题。

对于4KB页(页内偏移为12位),Cache索引可以使用的位数受限于:

索引位数+块内偏移位数12\text{索引位数} + \text{块内偏移位数} \leq 12

以64字节的Cache行为例,块内偏移为6位,因此索引最多为126=612 - 6 = 6位,对应64个组。如果Cache是WW路组相联,则最大无别名容量为:

最大容量=64×64×W=4096×W(字节)\text{最大容量} = 64 \times 64 \times W = 4096 \times W \text{(字节)}

即最大容量等于页大小 ×\times 路数。对于4KB页:

  • 4-way:最大4×4KB=16KB4 \times 4\text{KB} = 16\text{KB}无别名;

  • 8-way:最大8×4KB=32KB8 \times 4\text{KB} = 32\text{KB}无别名;

  • 16-way:最大16×4KB=64KB16 \times 4\text{KB} = 64\text{KB}无别名。

这解释了为什么现代处理器的L1 D-Cache几乎一律配置为32KB 8-way64KB 16-way——恰好等于页大小×路数\text{页大小} \times \text{路数},从而在无别名的前提下实现TLB与Cache的完全并行。

硬件描述 4 — 为什么L1 D-Cache是32KB 8-way

32KB、8-way组相联、64字节行的L1 D-Cache有64组,每组的索引使用地址的[11:6]位——恰好全部落在4KB页的页内偏移范围内([11:0]位)。因此,虚拟地址的索引位和物理地址的索引位完全相同,VIPT等价于PIPT,不存在任何别名问题。这就是"VIPT behaves as PIPT"条件——绝大多数现代处理器的L1 D-Cache都精心设计以满足这一条件。

值得注意的是,Apple和ARM的某些处理器使用16KB页(如iOS/macOS的默认页大小),此时无别名条件变为:容量 \leq 16KB ×\times 路数。16KB页、4-way即可支持64KB的无别名L1 D-Cache,Apple M系列处理器正是利用了这一优势。

VIPT的别名问题

当L1 D-Cache的容量超过页大小×路数\text{页大小} \times \text{路数}时,Cache索引的某些位位于页内偏移之上——这些位在虚拟地址和物理地址中可能不同,导致同一个物理位置可能被映射到Cache的不同组中,形成别名(alias)或同义词(synonym)问题。

别名问题的具体表现

假设一个64KB、4-way的L1 D-Cache使用4KB页。Cache有256个组,索引使用地址的[13:6]位。其中[11:6]位落在页内偏移范围内,虚拟地址和物理地址一定相同;但[13:12]位位于VPN范围内,虚拟地址和物理地址可能不同。

这种情况在实际系统中并非罕见——操作系统中的共享内存(mmapMAP_SHARED标志)、copy-on-write页面、以及内核与用户空间对同一物理页的不同虚拟地址映射都可能触发别名。

如果两个不同的虚拟地址VA1VA_1VA2VA_2映射到同一个物理地址PAPA,但VA1[13:12]VA2[13:12]VA_1[13:12] \neq VA_2[13:12],则:

  • 通过VA1VA_1访问时,数据被放入VA1[13:6]VA_1[13:6]索引对应的组;

  • 通过VA2VA_2访问时,数据被放入VA2[13:6]VA_2[13:6]索引对应的组。

结果是同一个物理数据在Cache中存在两个副本,分布在不同的组中。如果对VA1VA_1进行写入,VA2VA_2对应的副本不会被更新,导致数据不一致。

别名问题的解决方法

(1)限制Cache容量:最简单的方案——将L1 D-Cache的容量限制在页大小×路数\text{页大小} \times \text{路数}以内,从根本上消除别名。这是绝大多数处理器采用的方法。

(2)页着色(Page Coloring):操作系统在分配物理页时,确保映射到同一虚拟页的物理页的"颜色"(即物理地址中可能引起别名的位)与虚拟地址一致。这样虽然Cache索引使用了页内偏移之上的位,但由于操作系统保证了这些位在虚拟地址和物理地址中相同,不会产生别名。页着色的缺点是限制了操作系统的内存分配自由度,可能导致物理内存碎片化。

(3)硬件反别名机制:某些处理器(如早期的SPARC和某些ARM核心)在Cache中实现了硬件反别名逻辑。当检测到潜在别名时(例如Cache缺失填充时发现同一物理地址已存在于另一个组中),硬件自动失效或合并冲突的Cache行。这种方式增加了硬件复杂度,但对软件透明。

(4)使用更大的页:如果使用16KB页或64KB页,页内偏移更宽,无别名条件变为容量页大小×路数\text{容量} \leq \text{页大小} \times \text{路数}。以16KB页、4-way为例,可以支持64KB的无别名L1 D-Cache。ARM的处理器在配合16KB或64KB页时,可以使用更大的L1 Cache而不产生别名。这也是Apple选择16KB作为默认页大小的硬件层面原因之一——它同时降低了TLB压力并放宽了L1 Cache的无别名约束。

设计权衡 4 — L1 D-Cache容量与别名的权衡

在4KB页的约束下:

  • 32KB 8-way:无别名,这是Intel和AMD的标准选择。

  • 48KB 12-way:无别名,ARM Cortex-X3/X4采用此配置。

  • 64KB 16-way:无别名,但16-way的比较器和多路选择器增加了延迟。部分ARM核心和RISC-V核心采用此配置。

  • 64KB 4-way:有别名问题(64KB>4KB×4=16KB64\text{KB} > 4\text{KB} \times 4 = 16\text{KB}),需要页着色或硬件反别名。早期某些SPARC和PA-RISC处理器采用此设计。

2030年代的趋势是增大L1 D-Cache的路数而非总容量:从8-way增加到12-way或16-way,在保持无别名的前提下从32KB增大到48KB或64KB。另一个趋势是推动操作系统采用16KB或64KB页——Apple已经在iOS/macOS上默认使用16KB页,Android从2024年开始也支持16KB页。更大的页既可以减少TLB压力,又可以在无别名条件下支持更大的L1 Cache。

Apple 16KB页的系统级优势

Apple选择16KB作为iOS/macOS的默认页大小,从TLB和Cache设计的角度来看是一个系统级优化决策。16KB页相比4KB页带来了以下协同优势:

  1. TLB Reach提升4倍:同样72项的L1 dTLB,TLB Reach从72×4KB=288KB72 \times 4\text{KB} = 288\text{KB}提升到72×16KB=1152KB=1.125MB72 \times 16\text{KB} = 1152\text{KB} = 1.125\text{MB}。这使得大多数iOS应用的工作集完全被L1 dTLB覆盖。

  2. L1 D-Cache无别名约束放宽容量页大小×路数=16KB×4=64KB\text{容量} \leq \text{页大小} \times \text{路数} = 16\text{KB} \times 4 = 64\text{KB}。仅需4路即可支持64KB的无别名L1 D-Cache——比4KB页下的16路降低了4倍的路数需求。更少的路数意味着更简单的多路选择器和更短的时序路径。Apple M系列的128KB L1 D-Cache可以用8路实现无别名(16KB×8=128KB16\text{KB} \times 8 = 128\text{KB})。

  3. 页表遍历次数减少:16KB页的页内偏移为14位(比4KB多2位),每级页表仅索引14offset_bits14 - \text{offset\_bits}位VPN。对于48位VA,AArch64的16KB页使用3级页表(而非4KB页的4级),PTW最坏情况从4次降到3次内存访问。

  4. iTLB页边界跨越频率降低:取指窗口跨越页边界的频率从每4096字节一次降到每16384字节一次,降低了4倍。

当然,16KB页的代价是内存碎片——每个不足16KB的内存分配仍然占用一个完整的16KB页,浪费的平均内存为8KB/页(对比4KB页的2KB/页)。但对于现代设备(数GB到数十GB的RAM),这一碎片开销是完全可接受的。Android从2024年开始也支持16KB页作为可选配置,推动整个移动生态向16KB页迁移。

值得注意的是,L1 I-Cache的别名问题不如L1 D-Cache严重。I-Cache是只读的(指令不会被通过I-Cache写入),因此即使存在别名(同一段代码的两个虚拟副本),也不会导致数据不一致——仅仅是浪费了Cache容量。某些处理器因此允许L1 I-Cache的容量超过页大小×路数\text{页大小} \times \text{路数}的限制,接受少量的I-Cache别名以换取更大的有效容量。但这种做法在存在自修改代码(self-modifying code)或JIT编译的场景下需要额外的一致性保证。

将TLB和Cache放入流水线

在现代高性能处理器中,TLB和Cache的访问被精心地嵌入到执行流水线中,以最小化加载指令的延迟。这一设计是处理器性能的关键——L1 D-Cache的加载延迟直接决定了指针追踪(pointer chasing)等序列化访存模式的性能,也影响着乱序执行窗口的有效利用率。

典型的TLB-Cache流水线

图 11.8展示了现代处理器中加载指令的典型流水线安排。

加载指令在VIPT Cache中的流水线安排
加载指令在VIPT Cache中的流水线安排

这个4周期的加载延迟流水线是现代高性能处理器的标准配置:

  1. 周期1——地址生成(AGU):将基址寄存器加上立即数偏移,计算出虚拟地址。

  2. 周期2——TLB查找与Cache索引并行:虚拟地址的VPN部分送入TLB进行转换;同时,虚拟地址的低位(页内偏移部分)送入Cache SRAM阵列进行索引,读出被选中组的所有路的Tag和Data。

  3. 周期3——Tag比较与选路:TLB返回的PPN与Cache中读出的各路Tag进行比较,确定命中的路,并从预读出的Data中选择对应路的数据。

  4. 周期4——数据对齐与旁路转发:将选中的Cache行中的数据按照加载宽度和地址的低位进行对齐(例如从64字节的Cache行中提取出需要的4字节或8字节),然后通过旁路网络转发给依赖该加载结果的后续指令。

3周期加载的优化

某些高性能处理器通过各种微架构技巧将加载延迟优化到3个周期:

  • 推测性选路:在Tag比较完成之前,根据地址的某些位推测性地选择一路数据。如果推测正确,节省一个周期;如果推测错误,需要从正确的路重新读取数据(额外延迟约1–2个周期)。Intel的某些微架构使用了类似的way prediction技术。

  • 合并AGU和TLB:将地址生成和TLB查找合并在同一个周期内完成。这需要TLB足够快(可能需要限制TLB的容量或路数),或者使用加法器与CAM查找的延迟重叠技术。

  • 缩短Cache SRAM的读取时间:使用更快的SRAM单元或更小的Cache阵列来缩短Cache读取延迟。

Apple的M系列处理器(Firestorm/Avalanche核心)据信实现了3周期的L1 D-Cache加载延迟,这是其在整数性能上领先的原因之一——更短的加载延迟意味着指针追踪链的执行速度更快,在mcf等访存密集型基准测试中优势明显。

推测性TLB预填充

现代处理器将TLB预填充与硬件预取器相结合:当预取器检测到规律的地址流模式并发出预取请求时,如果预取目标地址不在TLB中,处理器会提前触发PTW来填充TLB表项,而不是等到实际的加载/存储指令执行时才发现TLB缺失。这种"预取驱动的TLB预填充"(prefetch-triggered TLB prefill)可以将TLB缺失的延迟从关键路径上移除。

这一优化在数组遍历、矩阵运算等具有可预测地址模式的工作负载中效果显著,可以将TLB缺失引起的性能损失降低50%以上。但对于指针追踪(pointer chasing)等不可预测的地址模式,预取器无法提前预测目标地址,TLB预填充也就无法发挥作用。

Intel的某些微架构还实现了推测性TLB访问(speculative TLB access):在分支预测后、指令提交前就开始进行TLB查找和可能的PTW。如果分支预测正确,TLB结果可以立即使用;如果预测错误,推测性填充的TLB表项仍然保留(因为TLB不需要精确维护——多余的TLB表项只是浪费了容量,不影响正确性),这与Cache的推测性填充不同。

存储指令的TLB-Cache流水线

存储指令的流水线与加载指令有所不同。存储指令需要两次地址转换(如果存储地址不在dTLB中)和一次Cache写入。但由于存储指令可以将数据先写入Store Buffer,不阻塞后续指令的执行,因此存储指令的TLB-Cache延迟对性能的影响不如加载指令那么直接。

存储指令的典型流水线如下:

  1. 地址生成:计算存储目标的虚拟地址。

  2. TLB查找:将虚拟地址转换为物理地址。如果TLB缺失,触发页表遍历。

  3. 权限检查:验证当前特权级别是否有写入权限,检查页的脏位(Dirty bit)。如果Dirty位未设置且当前是写操作,某些处理器需要触发一次特殊的页表遍历来设置Dirty位(称为"Dirty bit walk"),这会引入额外的延迟。RISC-V的Svadu扩展允许硬件自动设置A/D位,避免了这一额外遍历。

  4. 写入Store Buffer:将物理地址和数据写入Store Buffer。Store Buffer在指令退休时才将数据写入L1 D-Cache。

需要注意的是,存储指令虽然不在加载的关键延迟路径上,但Store-to-Load Forwarding(存储到加载的转发)机制需要在Store Buffer中按物理地址匹配——这意味着后续的加载指令需要使用物理地址(经TLB转换后)在Store Buffer中搜索是否有尚未写入Cache的存储数据可以直接转发。如果加载和存储的TLB查找不能在同一周期内完成,转发的延迟会增加。这是dTLB必须维持极低延迟的又一个重要原因。

TLB缺失对流水线的影响

当TLB缺失发生时,加载或存储指令无法完成地址转换,必须等待页表遍历的结果。在乱序处理器中,这条指令被标记为"等待TLB填充"状态,后续不依赖该指令结果的指令可以继续执行——这是乱序执行对TLB缺失延迟容忍能力的体现。

然而,如果TLB缺失导致的等待时间过长,ROB可能被填满(因为缺失的指令无法退休,ROB中的后续指令都被阻塞),此时整个流水线将暂停。这就是TLB缺失成为性能瓶颈的机制——它不仅增加了单条指令的延迟,还可能阻塞整个流水线。

图 11.9展示了TLB缺失对乱序流水线的影响时序。

TLB缺失对乱序流水线的影响
TLB缺失对乱序流水线的影响

TLB缺失的微架构处理细节

当L1 dTLB发生缺失时,处理器的精确处理流程如下(以乱序处理器为例):

  1. L1 TLB Miss检测(周期0):Load/Store指令在L1 dTLB中未命中。指令被标记为"TLB pending"状态,放入一个TLB Miss Buffer(也称为TLB Miss Queue)。

  2. L2 sTLB查询(周期1–7):TLB Miss Buffer将请求转发到L2 sTLB进行查找。L2 sTLB的组相联查找需要4–8个周期。

  3. L2 sTLB命中(周期7):如果L2 sTLB命中,翻译结果被写回L1 dTLB(TLB Fill),原始的Load/Store指令可以重新执行。从L1 Miss到L2 Hit的总额外延迟约6–8个周期。

  4. L2 sTLB也Miss(周期7):如果L2 sTLB也Miss,请求被转发到硬件PTW。PTW开始遍历页表。

  5. PTW遍历(周期7–50+):PTW按照页表级别逐级访问Cache/内存中的页表项。每级访问的延迟取决于页表项在Cache层次中的位置:L1 Cache命中约4周期,L2 Cache命中约12周期,L3 Cache命中约40周期,DRAM访问约200周期。4级遍历的最佳情况约16周期,最坏情况约800周期。

  6. TLB Fill(遍历完成后):PTW将最终的VPN\toPPN映射写入L1 dTLB和/或L2 sTLB。

  7. 指令重执行:等待该TLB翻译的Load/Store指令被唤醒,重新进入AGU/TLB/Cache流水线执行。

在整个过程中,乱序执行引擎继续处理不依赖于该Load/Store结果的后续指令。ROB(Re-order Buffer)中该Load/Store之后的独立指令可以继续执行和写回结果,但不能退休(因为ROB必须按序退休)。当ROB中积累了足够多的未退休指令而填满时,前端取指和分派被暂停——这就是TLB Miss导致流水线暂停的完整机制。

完整的地址转换流水线

将前面讨论的所有组件综合起来,图图 11.10展示了一个完整的现代处理器地址转换与缓存访问流水线。

完整的地址转换与缓存访问流水线
完整的地址转换与缓存访问流水线

这一完整流水线清晰地展示了TLB设计中的层次化思想:L1 TLB提供1周期的快速路径覆盖绝大多数访问,L2 sTLB以4–8周期的延迟捕获L1的溢出,硬件PTW配合PWC在20–200+周期内处理完全缺失。每一级都是为了减少对下一级(更慢、更昂贵)的依赖。

案例研究 4 — AMD Zen 4的TLB-Cache流水线

AMD Zen 4微架构的加载流水线配置如下:

  • L1 dTLB:72项全相联,1周期命中延迟。支持4KB/2MB/1GB三种页大小。

  • L1 D-Cache:32KB,8-way,VIPT,4周期load-to-use延迟。

  • L2 sTLB:2048项,8-way组相联,约6周期命中延迟。

  • L2 Cache:1MB,8-way,PIPT,约12周期命中延迟。

  • PTW:2个并行PTW,支持PWC(PML4/PDP/PD三级缓存)。

在最佳情况下(L1 dTLB命中 + L1 D-Cache命中),加载延迟为4周期。在L1 dTLB缺失但L2 sTLB命中的情况下,加载延迟增加约6个周期(L2 sTLB的访问延迟),总计约10周期。在L2 sTLB也缺失、需要PTW的情况下,如果PTW的所有级别都在L2 Cache中命中(12周期/级 ×\times 4级),加载延迟约为48+10=58个周期。

Zen 4的一个关键优化是推测性TLB预填充:当硬件预取器检测到规律的地址模式时,不仅预取数据到Cache,还预先触发TLB填充,确保后续访问在TLB中命中。这一优化对于大内存遍历(如数据库扫描、数组遍历)特别有效。

全相联TLB的SystemVerilog实现

为了将TLB的CAM匹配和PLRU替换从概念转化为可综合的硬件描述,我们给出一个简化的全相联TLB模块。数学上,全相联TLB的查找可以表示为:对于输入的搜索键k=(ASID,VPN)k = (\text{ASID}, \text{VPN}),TLB返回匹配的条目索引ii^*和对应的PPN:

i=arg0i<N[valid[i](tag[i]=k)],PPNout=ppn[i]i^* = \underset{0 \le i < N}{\arg} \bigl[\text{valid}[i] \land (\text{tag}[i] = k)\bigr], \qquad \text{PPN}_{\text{out}} = \text{ppn}[i^*]

其中NN是TLB条目数。PLRU替换策略使用N1N-1位的树形结构,每次访问更新从叶子到根路径上的方向位。以下代码展示了CAM匹配和PLRU替换的核心逻辑:

verilog
module fa_tlb #(
    parameter N_ENTRIES = 64,
    parameter VPN_W     = 36,
    parameter ASID_W    = 16,
    parameter PPN_W     = 40
)(
    input  logic                clk, rst_n,
    // 查找接口
    input  logic [VPN_W-1:0]    lookup_vpn,
    input  logic [ASID_W-1:0]   lookup_asid,
    output logic                hit,
    output logic [PPN_W-1:0]    hit_ppn,
    output logic [5:0]          hit_attr,  // RWX, U, D, A
    // 填充接口 (来自PTW)
    input  logic                fill_valid,
    input  logic [VPN_W-1:0]    fill_vpn,
    input  logic [ASID_W-1:0]   fill_asid,
    input  logic [PPN_W-1:0]    fill_ppn,
    input  logic [5:0]          fill_attr,
    // 失效接口
    input  logic                inv_all,
    input  logic                inv_asid_en,
    input  logic [ASID_W-1:0]   inv_asid
);

  // --- 存储阵列 ---
  logic [N_ENTRIES-1:0]         valid;
  logic [VPN_W-1:0]             tag_vpn  [N_ENTRIES];
  logic [ASID_W-1:0]            tag_asid [N_ENTRIES];
  logic [PPN_W-1:0]             data_ppn [N_ENTRIES];
  logic [5:0]                   data_attr[N_ENTRIES];

  // --- CAM 并行匹配 ---
  logic [N_ENTRIES-1:0] match;
  always_comb begin
    for (int i = 0; i < N_ENTRIES; i++) begin
      match[i] = valid[i]
               & (tag_vpn[i]  == lookup_vpn)
               & (tag_asid[i] == lookup_asid);
    end
  end

  // 命中检测与优先编码
  assign hit = |match;
  logic [$clog2(N_ENTRIES)-1:0] hit_idx;
  always_comb begin
    hit_idx = '0;
    for (int i = N_ENTRIES-1; i >= 0; i--)
      if (match[i]) hit_idx = i[$clog2(N_ENTRIES)-1:0];
  end
  assign hit_ppn  = data_ppn[hit_idx];
  assign hit_attr = data_attr[hit_idx];

  // --- 树形PLRU替换 ---
  logic [N_ENTRIES-2:0] plru_tree;  // N-1位方向树

  function automatic [$clog2(N_ENTRIES)-1:0] plru_victim(
      input logic [N_ENTRIES-2:0] tree);
    logic [$clog2(N_ENTRIES)-1:0] idx;
    int node = 0;
    for (int level = 0; level < $clog2(N_ENTRIES); level++) begin
      if (tree[node]) begin
        node = 2 * node + 2;  // 右子树
        idx[($clog2(N_ENTRIES)-1)-level] = 1'b1;
      end else begin
        node = 2 * node + 1;  // 左子树
        idx[($clog2(N_ENTRIES)-1)-level] = 1'b0;
      end
    end
    return idx;
  endfunction

  // PLRU更新:命中时翻转从叶到根的路径
  task automatic plru_update(
      input [$clog2(N_ENTRIES)-1:0] access_idx);
    int node = 0;
    for (int level = 0; level < $clog2(N_ENTRIES); level++) begin
      if (access_idx[($clog2(N_ENTRIES)-1)-level]) begin
        plru_tree[node] <= 1'b0;  // 指向反方向
        node = 2 * node + 2;
      end else begin
        plru_tree[node] <= 1'b1;
        node = 2 * node + 1;
      end
    end
  endtask

  // --- 主控逻辑 ---
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      valid     <= '0;
      plru_tree <= '0;
    end else if (inv_all) begin
      valid <= '0;  // 全局刷新
    end else begin
      // 按ASID选择性失效
      if (inv_asid_en) begin
        for (int i = 0; i < N_ENTRIES; i++)
          if (tag_asid[i] == inv_asid)
            valid[i] <= 1'b0;
      end
      // TLB填充
      if (fill_valid && !hit) begin
        automatic logic [$clog2(N_ENTRIES)-1:0] vic;
        vic = plru_victim(plru_tree);
        valid[vic]     <= 1'b1;
        tag_vpn[vic]   <= fill_vpn;
        tag_asid[vic]  <= fill_asid;
        data_ppn[vic]  <= fill_ppn;
        data_attr[vic] <= fill_attr;
        plru_update(vic);
      end
      // 命中时更新PLRU
      if (hit) plru_update(hit_idx);
    end
  end
endmodule

这段代码展示了TLB设计的两个核心环节:(1)CAM并行匹配——match信号通过NN个并行比较器同时检查所有条目,时间复杂度为O(1)O(1)但面积复杂度为O(N)O(N),这正是全相联TLB面积大的根本原因;(2)树形PLRU——用N1N-1位方向树近似LRU,每次访问仅需更新log2N\log_2 N位(64项TLB只更新6位),远少于真正LRU所需的Nlog2NN \log_2 N位排序更新。在真实的RTL实现中,还需要处理多端口(iTLB和dTLB独立访问)、大页掩码、全局位(G位跳过ASID比较)等额外逻辑,但核心的匹配-替换模式与上述代码一致。

性能分析 5 — 全相联TLB功耗的五步定量分析

Setup. 计算一个64项全相联L1 dTLB在5nm工艺、4GHz频率下每次查找的功耗。TLB表项的CAM比较宽度为48位(36位VPN + 12位ASID/PCID)。

Strategy. 从单个CAM比较位的切换功耗出发,逐步推导到完整TLB阵列的每次查找功耗和持续功耗。

Derivation.

  1. 单比较位切换功耗:5nm工艺下,一个CAM比较位的动态切换功耗约为Ebit0.5fJE_{\text{bit}} \approx 0.5\,\mathrm{fJ}(包括匹配线充放电和比较逻辑翻转)。

  2. 单条目比较功耗:每条目48个比较位,Eentry=48×0.5fJ=24fJE_{\text{entry}} = 48 \times 0.5\,\mathrm{fJ} = 24\,\mathrm{fJ}

  3. 每次查找功耗:全相联64项并行比较,Elookup=64×24fJ=1536fJ=1.54pJE_{\text{lookup}} = 64 \times 24\,\mathrm{fJ} = 1536\,\mathrm{fJ} = 1.54\,\mathrm{pJ}

  4. 每次查找的SRAM读取功耗:命中后读取PPN(40位)+ 属性(约10位),SRAM读取约0.8pJ0.8\,\mathrm{pJ}。总计每次查找约1.54+0.8=2.34pJ1.54 + 0.8 = 2.34\,\mathrm{pJ}

  5. 持续动态功耗:假设每周期1次dTLB查找(Load/Store),PdTLB=2.34pJ×4×109Hz=9.4mWP_{\text{dTLB}} = 2.34\,\mathrm{pJ} \times 4 \times 10^9\,\mathrm{Hz} = 9.4\,\mathrm{mW}

Interpretation. 9.4mW看似不多,但如果L2 sTLB也采用全相联(假设1024项,同样48位比较宽度),其CAM功耗将达到1024/64×1.54pJ24.6pJ1024/64 \times 1.54\,\mathrm{pJ} \approx 24.6\,\mathrm{pJ}每次查找。假设L2 sTLB每4个周期被访问一次(L1 Miss率约25%),持续功耗约24.6pJ×109=24.6mW24.6\,\mathrm{pJ} \times 10^9 = 24.6\,\mathrm{mW}。这解释了为什么L2 sTLB不能使用全相联——即使从功耗角度看也不可接受,更不用说面积。

Verification. 公开的处理器功耗分析(如ARM Cortex-A78的功耗分解)显示L1 dTLB的动态功耗约占核心总动态功耗的0.3–0.5%。对于一个2W动态功耗的核心,这对应6–10mW,与我们的估算一致。

性能分析 6 — TLB缺失对IPC的量化影响

我们可以构建一个简单的分析模型来量化TLB缺失对处理器IPC的影响。假设一个6-wide乱序处理器的基线IPC为3.0(无TLB缺失时),程序的访存指令比例为30%(加载20% + 存储10%),各级TLB参数如下:

  • L1 dTLB:72项,缺失率r1r_1,命中延迟已包含在基线中;

  • L2 sTLB:2048项,条件缺失率r2r_2(即L1缺失中进一步缺失的比例),命中延迟6周期;

  • PTW:平均遍历延迟35周期(有PWC加速)。

则TLB缺失导致的额外CPI为:

CPITLB=pmem×r1×(tL2TLB+r2×tPTW) \mathrm{CPI}_{\text{TLB}} = p_{\text{mem}} \times r_1 \times (t_{\text{L2TLB}} + r_2 \times t_{\text{PTW}})

其中pmem=0.30p_{\text{mem}} = 0.30。考虑两种典型工作负载:

Case 1:SPEC CPU整数(规律访存)r1=1%r_1 = 1\%r2=10%r_2 = 10\%

CPITLB=0.30×0.01×(6+0.10×35)=0.30×0.01×9.5=0.029\mathrm{CPI}_{\text{TLB}} = 0.30 \times 0.01 \times (6 + 0.10 \times 35) = 0.30 \times 0.01 \times 9.5 = 0.029

IPC从3.0降低到1/(1/3.0+0.029)=2.761/(1/3.0 + 0.029) = 2.76,下降约8%。

Case 2:大内存数据库(不规则访存)r1=5%r_1 = 5\%r2=30%r_2 = 30\%

CPITLB=0.30×0.05×(6+0.30×35)=0.30×0.05×16.5=0.248\mathrm{CPI}_{\text{TLB}} = 0.30 \times 0.05 \times (6 + 0.30 \times 35) = 0.30 \times 0.05 \times 16.5 = 0.248

IPC从3.0降低到1/(1/3.0+0.248)=1.691/(1/3.0 + 0.248) = 1.69,下降约44%!

这个计算清楚地说明了TLB设计对性能的巨大影响。对于数据库等大内存工作负载,TLB缺失可以成为最主要的性能瓶颈。这也解释了为什么数据库厂商强烈推荐使用2MB大页——使用2MB页可以将TLB的有效覆盖范围从72×4KB=288KB72 \times 4\text{KB} = 288\text{KB}扩大到72×2MB=144MB72 \times 2\text{MB} = 144\text{MB},大幅降低r1r_1

从处理器设计师的角度,降低TLB缺失对IPC的影响有以下几个杠杆:

  • 增大TLB容量(降低r1r_1r2r_2)——但受面积和功耗限制;

  • 降低PTW延迟(降低tPTWt_{\text{PTW}})——通过PWC和并行PTW实现;

  • 降低L2 sTLB延迟(降低tL2TLBt_{\text{L2TLB}})——通过优化组相联结构实现;

  • 支持大页(从根本上降低r1r_1)——需要ISA和OS的配合;

  • 增大ROB容量——让乱序执行能够跨越更长的TLB缺失延迟,减少流水线暂停。

iTLB与I-Cache的配合

iTLB的流水线安排与dTLB有所不同。取指单元每周期需要从一个虚拟PC地址取出一个Cache行的指令,iTLB的查找发生在取指流水线的最前端。现代处理器通常将iTLB查找与L1 I-Cache的索引并行进行,方式与VIPT D-Cache类似。

但iTLB面临一个dTLB不存在的挑战:取指可能跨越页边界。当一个64字节的取指窗口(fetch window)横跨两个4KB页的边界时,前半部分在一个页中,后半部分在另一个页中,需要两次独立的iTLB查找。处理这种情况有两种方式:

  1. 截断取指:当检测到取指窗口跨越页边界时,仅取出当前页内的部分指令,下一周期再取跨页的部分。这种方式简单但会降低取指带宽。

  2. 双端口iTLB:iTLB支持同一周期进行两次查找(或使用两个副本),可以同时处理页边界两侧的地址转换。代价是iTLB的面积和功耗翻倍。

大多数现代处理器采用截断取指方式。在4KB页的情况下,页边界每4096字节出现一次;如果平均指令长度为4字节(ARM/RISC-V的固定长度指令),约每1024条指令会遇到一次页边界(实际频率取决于代码对齐情况),对取指带宽的影响较小。但对于x86的变长指令(1–15字节,平均约4字节),页边界跨越更不规律,且由于指令长度不对齐,可能出现单条指令跨越页边界的情况。使用16KB或64KB页可以进一步降低页边界跨越的频率,Apple选择16KB默认页大小的一个被忽视的好处就是减少了iTLB页边界跨越的频率——每16384字节才出现一次,比4KB页降低了4倍。

TLB Miss的级联效应

TLB Miss对性能的影响不仅限于单条指令的延迟增加,还可能触发级联效应。在乱序处理器中,一次TLB Miss导致的Load暂停可能引发以下连锁反应:

  1. ROB阻塞:TLB Miss的指令无法退休,占据ROB条目。如果PTW延迟很长(如虚拟化环境下的嵌套遍历,超过200周期),ROB可能在PTW完成之前就被后续指令填满,导致前端暂停(front-end stall)。

  2. Store Buffer饱和:如果Store指令发生TLB Miss,该Store无法将物理地址写入Store Buffer(因为物理地址尚未确定)。后续的Store指令也可能因为Store Buffer容量限制而无法发射。

  3. Load-Load依赖暴露:在Memory Dependence Prediction(内存依赖预测)中,如果预测器认为某个Load不依赖于之前的Store(因此可以提前执行),但该Store的TLB Miss导致其物理地址尚不可知,Load的执行结果可能需要在Store完成后重新验证。

  4. 预取器效率下降:硬件预取器的流检测依赖于连续的Cache访问模式。TLB Miss导致的访问中断可能破坏预取器的步长检测,使预取器暂时失效或预取到错误的地址。

这些级联效应使得TLB Miss的实际性能影响大于简单的"增加PTW延迟"——它可能导致乱序执行引擎的有效利用率从90%以上下降到50%甚至更低。这也是为什么增大ROB容量(使乱序窗口更大,能够跨越更长的TLB Miss延迟)是与TLB优化互补的重要设计方向。

本章讨论了TLB的核心设计要素:从基本的CAM/组相联结构选择,到多级TLB层次的容量规划,再到PTW、PWC等缺失处理机制,最后到TLB与Cache在流水线中的协同设计。TLB设计的核心目标可以概括为一句话:以尽可能低的延迟和功耗,为尽可能多的地址转换提供命中。在2030年代,随着工作负载的内存工作集持续增长(云计算虚拟机的内存配置已达数百GB甚至TB级别)、页表层次可能进一步加深(5级页表已在x86和RISC-V中出现),以及安全隔离需求(如CET、MTE等)对TLB表项格式提出新要求,TLB的设计将面临更大的挑战。

面向2030年代的TLB设计,以下几个方向值得关注:

  1. TLB容量的持续增长。随着工作负载的内存工作集增长和页表深度增加,L2 sTLB可能从当前的2048–4096项增长到8192项甚至更大。这一趋势已经在近年的产品中可以观察到:AMD Zen 5将L2 sTLB从Zen 4的2048项增加到4096项,Apple M3达到4096项。更大的L2 sTLB需要更高效的组相联结构(可能采用16-way甚至32-way)和更先进的功耗优化技术(如TLB banking和分阶段匹配)。

  2. TLB Coalescing。TLB Coalescing(TLB合并)是一种将多个连续4KB页的TLB表项合并为一个大范围表项的技术。例如,如果操作系统将512个连续的4KB物理页映射到512个连续的虚拟页(这在实际中很常见,因为操作系统通常以更大的粒度进行物理内存分配),硬件可以将这512个表项合并为一个等效的2MB大页表项。这使得TLB的有效容量倍增而不需要使用操作系统显式配置的大页。Intel从Skylake开始支持TLB Coalescing,将其称为"Page Walk Coalescing"。

  3. 安全扩展对TLB的影响。ARM的Memory Tagging Extension(MTE)要求在每次内存访问时检查4位的标签,这些标签信息可能需要存储在TLB表项中或通过独立的标签缓存查找。Intel的CET(Control-flow Enforcement Technology)引入了影子栈,影子栈的地址转换同样需要TLB支持。这些安全扩展增加了TLB表项的宽度和查找逻辑的复杂度。更深层的影响是Spectre/Meltdown类漏洞的持续威胁——KPTI(Kernel Page Table Isolation)要求内核态和用户态使用不同的页表(不同的PCID/ASID),实质上将TLB的有效容量减半(因为内核和用户空间的TLB条目独立存储)。这一安全开销是常态化的——不像性能优化可以在需要时启用,安全缓解措施一旦部署就不能轻易关闭。处理器设计者需要在TLB容量规划中将KPTI的容量减半效应纳入考虑。

  4. 异构页大小的软硬件协同。未来的操作系统可能支持更灵活的页大小配置——例如允许用户空间使用16KB默认页(如macOS/iOS),内核空间使用2MB大页,设备内存使用64KB页。TLB需要高效地支持这种混合页大小的工作负载,在不同页大小之间动态平衡容量分配。

  5. TLB与CXL/UCIe的交互。CXL(Compute Express Link)技术允许CPU访问通过CXL接口连接的远端内存。CXL内存的延迟(约150–300ns)高于本地DRAM(约60–100ns),但低于传统的远程NUMA节点。CXL内存的地址翻译可以通过与本地内存相同的页表和TLB机制处理,但TLB Miss触发的页表遍历如果需要访问CXL内存中的页表项,延迟将显著增加。处理器可能需要为CXL内存的页表项提供更大的PWC缓存,或者优化PTW的内存访问调度以区分本地和远程内存。

  6. L3 TLB的出现。随着L2 sTLB的容量增长到4096项以上,其面积和功耗也逐渐接近极限。一些学术提案建议引入L3 TLB——一个更大(例如16384项或更多)但更慢(约20–30周期命中延迟)的第三级TLB。L3 TLB可以覆盖L2 sTLB之外的地址翻译需求,在极大工作集的工作负载中减少对PTW的依赖。但L3 TLB的性价比分析需要仔细考虑——如果页表遍历通过PWC和Data Cache已经足够快速(20–40周期),L3 TLB相比之下的收益可能有限。

TLB设计中的专家洞察

在结束本章之前,我们用两个较深入的分析来展示TLB设计中那些不那么显而易见但至关重要的考量。

TLB的投机性与正确性保证

TLB缓存的一个微妙特性是:TLB条目不需要精确维护。与数据Cache不同——Cache中的数据必须与内存保持一致(否则程序看到错误的值),TLB中的映射与页表之间可以暂时不一致,只要满足以下条件:

  1. 不存在旧映射仍可访问的安全漏洞。当操作系统解除映射(munmap)或修改权限(mprotect)时,必须确保TLB中的旧条目被及时失效(通过TLB shootdown)。这是安全性要求,不可妥协。

  2. 额外的TLB条目仅浪费容量,不影响正确性。如果一个TLB条目缓存了一个仍然有效但不再是"最近使用"的映射,它只是占用了TLB容量,不会导致错误。这与Cache中的脏数据不同——脏Cache行必须在被驱逐时写回内存,而TLB条目可以被简单丢弃。

这一特性使得TLB可以投机性地填充——处理器在分支预测之后、投机执行期间发生的TLB Miss,其PTW结果可以填充到TLB中。即使后来分支预测被发现是错误的、投机执行被取消,已填充的TLB条目不需要被回滚(unlike speculative Cache fill, which is more complex to handle)。这种"TLB的投机不变性"是一个重要的简化设计的性质。

然而,Spectre和Meltdown等微架构安全漏洞证明了投机性TLB填充也有安全风险——攻击者可以利用投机执行中的TLB行为来推断受保护内存的地址映射。这就是为什么现代处理器在投机性TLB填充时也需要进行权限检查,确保投机访问不会在TLB中留下攻击者可以探测到的侧信道痕迹。

TLB设计空间的第一性原理分析

处理器架构师在设计TLB时面临一个多维优化问题。让我们从第一性原理出发,推导TLB设计的关键约束和最优点。

TLB容量的信息论下界

从信息论的角度,一个程序的虚拟地址映射可以被看作一个从VPN集合到PPN集合的函数f:VPNPPNf: \text{VPN} \to \text{PPN}。TLB缓存的是这个函数的部分值。程序在任意时刻的"翻译工作集"大小WW(以页为单位)决定了TLB需要的最小容量。

如果TLB容量C<WC < W,则每WW次不同页面的访问中至少有WCW - C次TLB Miss(冷启动缺失)。在稳态下,TLB的缺失率受限于:

rmissWCW=1CW(C<W) r_{\text{miss}} \geq \frac{W - C}{W} = 1 - \frac{C}{W} \quad (\text{当} C < W)

这是TLB缺失率的下界——即使使用最优的替换策略(OPT/Belady),缺失率也不可能低于这个值。在实际中,由于替换策略的非最优性和地址分布的不均匀性,真实缺失率通常高于下界1.5–3倍。

这一分析的实际意义是:对于工作集W=1000W = 1000页(4MB)的程序,72项L1 dTLB的缺失率下界为172/1000=92.8%1 - 72/1000 = 92.8\%——几乎每次访问都Miss!但这个下界忽略了时间局部性——程序不是均匀地访问1000个不同页面,而是在短时间内集中访问少数几个"热"页面。L1 dTLB正是利用这种时间局部性,在仅72项的容量下实现98–99%的命中率。

L1 TLB延迟约束的物理极限

L1 TLB必须在1个时钟周期内完成查找。在5GHz处理器上,1个周期仅有200ps。在这200ps内,L1 TLB需要完成:

  1. ASID和VPN的CAM比较(约100–120ps,取决于比较宽度和CAM优化程度)。

  2. 优先编码器确定命中的条目(约20–30ps)。

  3. 从命中条目的SRAM中读出PPN和属性(约30–50ps)。

  4. 信号传播和设置/保持时间余量(约20–30ps)。

总计约170–230ps,恰好在200ps的时钟周期预算内——这就是为什么L1 TLB的设计如此紧凑。增加TLB容量(更多CAM条目)会增加步骤1的延迟(更长的匹配线、更大的电容负载),同时增加步骤2的延迟(更宽的优先编码器)。在5nm工艺、5GHz频率下,全相联L1 TLB的容量上限约为128–160项(取决于比较宽度和布局优化技术)。超过这一容量,L1 TLB将无法在1个周期内完成查找,需要流水线化(2周期命中延迟),这将直接增加Load-to-Use延迟——代价通常不可接受。

TLB容量-命中率的经验曲线

基于大量工作负载的统计分析,L1 dTLB的命中率与容量之间存在一个经验关系:

hL1(C)1aCb h_{\text{L1}}(C) \approx 1 - \frac{a}{C^b}

其中aabb是工作负载相关的参数。对于典型的服务器工作负载,a0.5a \approx 0.5b0.7b \approx 0.7。使用这个模型:

  • C=32C = 32项:h10.5/320.7=10.5/12.7=96.1%h \approx 1 - 0.5/32^{0.7} = 1 - 0.5/12.7 = 96.1\%

  • C=72C = 72项:h10.5/720.7=10.5/23.2=97.8%h \approx 1 - 0.5/72^{0.7} = 1 - 0.5/23.2 = 97.8\%

  • C=160C = 160项:h10.5/1600.7=10.5/40.6=98.8%h \approx 1 - 0.5/160^{0.7} = 1 - 0.5/40.6 = 98.8\%

从32项增加到72项,命中率提升1.7个百分点(从96.1%到97.8%);从72项增加到160项(容量增加2.2倍),命中率仅提升1.0个百分点(从97.8%到98.8%)。这种递减的收益曲线是TLB设计者需要在容量和面积/功耗之间做出权衡的原因——Apple的160项dTLB是一个激进的选择,需要通过先进的CAM优化技术来控制功耗。

TLB与Cache的面积比较

一个经常被问到的问题是:为什么不把TLB做得像Cache一样大?答案在于CAM和SRAM的面积差异。

以5nm工艺为参考,一个32KB、8-way的L1 D-Cache使用SRAM单元,每单元约0.03μm20.03\,\mu\mathrm{m}^2,总存储容量32×1024×8=26214432 \times 1024 \times 8 = 262144位,面积约262144×0.037864μm20.008mm2262144 \times 0.03 \approx 7864\,\mu\mathrm{m}^2 \approx 0.008\,\mathrm{mm}^2(不含控制逻辑和解码器)。

相比之下,一个72项全相联L1 dTLB,每条目约100位(48位tag + 40位PPN + 12位属性),CAM部分(tag)每位约0.08μm20.08\,\mu\mathrm{m}^2,SRAM部分(data)每位约0.03μm20.03\,\mu\mathrm{m}^2。总面积约72×(48×0.08+52×0.03)72×(3.84+1.56)=72×5.4389μm20.0004mm272 \times (48 \times 0.08 + 52 \times 0.03) \approx 72 \times (3.84 + 1.56) = 72 \times 5.4 \approx 389\,\mu\mathrm{m}^2 \approx 0.0004\,\mathrm{mm}^2

72项TLB的面积仅为32KB Cache的约5%——这看起来TLB还有很大的扩展空间。

但为什么不直接把TLB做成32KB?如果将TLB看作一种"数据Cache"(缓存页表项而非数据块),32KB的TLB SRAM可以存储32×1024×8/100262132 \times 1024 \times 8 / 100 \approx 2621条TLB项——足以覆盖2621×4KB=10.2MB2621 \times 4\text{KB} = 10.2\text{MB}的地址空间,远超当前72项的288KB。但这里有一个本质区别:数据Cache使用组相联的SRAM查找(仅激活被索引的一个组),而全相联TLB使用CAM的并行比较(所有条目同时激活)。SRAM的查找功耗与路数成正比(8-way的32KB Cache每次仅激活8个SRAM行),而CAM的查找功耗与总条目数成正比(72项全相联TLB每次激活72个比较器)。如果将TLB扩大到2621项全相联,功耗增加2621/72362621/72 \approx 36倍。

这就是为什么大容量TLB(L2 sTLB)必须使用组相联结构——功耗从"与总容量成正比"变为"与路数成正比"。2048项8-way的L2 sTLB每次仅激活8个比较器(而非2048个),功耗仅为等容量全相联的8/2048=0.4%8/2048 = 0.4\%。代价是引入了冲突缺失——但8-way在2048项下的冲突缺失率不到全相联的0.5%,这个微小的命中率损失换来了250倍的功耗降低。

但问题在于功耗:全相联CAM的每次查找功耗约为组相联SRAM的16倍(参见本章前面的perfbox分析)。如果将TLB扩大到1024项全相联,每次查找的CAM翻转位增加1024/72141024/72 \approx 14倍,功耗从9.4mW增加到约130mW——在一个2W核心功耗预算中占6.5%,仅用于TLB查找。这就是为什么L2 sTLB必须使用组相联(功耗随路数而非总容量增长),以及为什么L1 TLB的容量被严格限制在32–160项的范围内。

本章开篇讲述的数据库性能异常案例,本质上是TLB容量不足、大页未启用的结果。我们在全相联TLB功耗分析中看到,TLB容量受限于CAM的面积和功耗——这正是第 3.0 章中讨论的先进工艺节点约束在TLB设计中的具体体现。TLB的层次化设计思想也与第 5.0 章中讨论的Cache层次结构高度同构——两者都是用"小容量快速 + 大容量慢速"的分层缓存来逼近"大容量且快速"的理想存储。

前向桥接。第 12.0 章中,我们将看到虚拟化如何将TLB设计推向更严峻的挑战:虚拟化环境下的嵌套页表遍历将TLB Miss的代价从4次内存访问放大到24次,TLB条目需要额外的VMID字段来区分不同虚拟机,而多个VM共享同一物理TLB导致的容量稀释效应使得本章讨论的所有优化——大页、TLB Coalescing、PWC——的重要性被进一步放大。理解了本章的TLB设计基础,我们才能真正理解虚拟化环境下地址转换的复杂度为何呈乘法而非加法增长。

正文与图片:CC BY-NC-SA 4.0 · 本仓库少量站点配置代码:MIT