4. TLB优化之页面大小:大页面(Superpage)支持、混合页面粒度对TLB性能的影响
各位做CPU的朋友,咱们今天聊一个很实在的话题——页面大小。说白了,就是TLB里那条表项到底管多大一块内存。
我记得刚入行那会儿,觉得4KB页面挺好用的,Linux默认就是这个。直到有一次做数据库加速器,发现TLB miss率高得离谱,性能死活上不去。后来一查,问题就出在页面粒度上。
4.1 为什么页面大小会影响TLB性能?
先想一个问题:TLB的容量是固定的,比如32条entry。每条entry能覆盖的内存范围越大,TLB能映射的总内存就越大。
举个例子:
- 4KB页面 × 32条 = 128KB 总映射
- 2MB大页面 × 32条 = 64MB 总映射
- 1GB巨页 × 32条 = 32GB 总映射
差距有多大?你想想看,一个数据库的工作集可能是几百MB甚至几个GB。用4KB页面,TLB根本装不下,miss率自然高。换成2MB大页面,一个TLB entry就能覆盖原来512个entry的工作量。
核心结论:页面越大,TLB覆盖范围越大,miss率越低。但大页面也有代价,后面会讲。
4.2 RISC-V的大页面支持
RISC-V的Sv39页表格式,天然支持多种页面粒度。我来给你拆解一下:
| 页表层级 | 页面大小 | PTE中的标志位 | 典型用途 |
|---|---|---|---|
| L0(第0级) | 1GB | R/W/X + D/A + V | 超大连续内存 |
| L1(第1级) | 2MB | R/W/X + D/A + V | 数据库、HPC |
| L2(第2级) | 4KB | R/W/X + D/A + V | 通用场景 |
关键点在于:RISC-V的PTE里有一个标志位,叫“叶子页表”。如果某级页表的PTE被标记为叶子,那它就直接映射一个大页面,不再往下走。
举个例子:L1页表的某个entry,如果被标记为叶子,那它映射的就是一个2MB的大页面。CPU在查TLB时,如果命中这条entry,就直接拿到物理地址,不需要再查L2页表。
我的经验:在实现TLB时,一定要检查PTE的叶子标志。我见过一个设计,硬件没处理好这个标志,导致大页面被当成中间页表继续往下查,结果TLB miss率反而更高了。
4.3 混合页面粒度:现实中的挑战
理想很丰满,现实很骨感。实际系统中,4KB、2MB、1GB的页面是混着用的。这就带来了几个问题:
4.3.1 TLB的同名冲突
TLB通常用虚拟地址的VPN(Virtual Page Number)来索引。不同粒度的页面,VPN的位数不一样:
- 4KB页面:VPN[38:12]
- 2MB页面:VPN[38:21]
- 1GB页面:VPN[38:30]
问题来了:同一个虚拟地址,可能同时被4KB页面和2MB页面映射。TLB里存的是哪条?查的时候怎么区分?
我建议的做法是:在TLB的tag里存一个页面粒度字段。查TLB时,先用VPN的低位去匹配粒度,再比较高位。这样就能区分不同粒度的entry。
4.3.2 大页面的碎片化问题
大页面需要连续的物理内存。系统跑久了,内存碎片化严重,2MB的连续空间都很难找。这时候,就算你想用大页面,也分配不出来。
我曾经踩过的坑:在一个长期运行的服务器上,启用了透明大页(THP)。结果系统在尝试合并4KB页面为2MB大页面时,触发了大量的内存迁移和TLB shootdown,性能反而下降了30%。后来我改成只在启动时预分配大页面,才解决问题。
4.4 混合粒度TLB的硬件设计
既然页面粒度是混着的,TLB硬件就得支持多粒度查找。我画了一张图,帮你理解:
这个设计的关键点:
- 分bank存储:不同粒度的TLB entry存在不同的bank里,避免互相干扰
- 并行查找:所有bank同时查,谁命中就用谁的结果
- 优先级:如果多个bank同时命中(比如4KB和2MB都映射了同一地址),以小粒度为准
4.5 软件层面的优化策略
硬件设计好了,软件也得配合。我总结了几条实战经验:
- 启动时预分配大页面:在系统启动阶段,预留一块连续的物理内存,专门给大页面用。避免运行时碎片化问题。
- 关键应用绑定大页面:数据库、HPC这类工作集大的应用,通过mmap的MAP_HUGETLB标志,强制使用2MB或1GB页面。
- 监控TLB miss率:用perf工具监控TLB miss率。如果miss率超过5%,就该考虑启用大页面了。
- 避免过度使用:不是所有场景都适合大页面。小页面在内存分配灵活性和碎片控制上更有优势。
性能调优口诀:大页面降miss,小页面省内存。混合粒度是常态,硬件软件要配合。
4.6 实际项目中的案例
我记得有一次做RISC-V的AI加速器,TLB miss率高达15%。查了之后发现,模型参数和权重数据分布在4KB页面上,TLB根本装不下。
解决方案:
- 把模型参数所在的虚拟地址空间,映射到2MB大页面上
- TLB miss率从15%降到了2%
- 推理性能提升了40%
但要注意,大页面不是银弹。另一个项目中,我尝试把所有内存都换成2MB页面,结果内存碎片率飙升,系统运行一段时间后连4KB页面都分配不出来了。
避坑指南:我曾经在一个项目中,为了追求极致的TLB命中率,把所有内存都映射成1GB大页面。结果TLB miss率确实降到了0.1%,但内存利用率只有60%。因为很多小对象根本用不了那么大空间,白白浪费了。后来我改成“大页面为主,小页面为辅”的策略,才平衡了性能和内存效率。
4.7 小结
页面大小对TLB性能的影响,说白了就是“覆盖范围”和“灵活性”的权衡。大页面能大幅降低TLB miss率,但会带来碎片化和分配困难的问题。混合粒度是实际系统中的常态,硬件要支持多粒度并行查找,软件要做好页面分配策略。
嗯,这一章就到这里。记住一句话:没有最好的页面大小,只有最适合的页面大小。