fifo 在真实访问序列下命中率常低于 lru,因其忽略时间局部性、按入队顺序淘汰,而程序多重复访问近期页;lru 依最近使用时间淘汰,更贴合实际访存模式,且 fifo 易导致刚换出即将重用的页而引发抖动。

为什么 FIFO 在真实访问序列下命中率常低于 LRU
因为 FIFO 完全忽略访问时间局部性,只按入队顺序淘汰,而实际程序往往重复访问近期用过的页(比如循环遍历数组、函数调用栈复用)。LRU 利用最近使用时间作为淘汰依据,更贴合真实访存模式。FIFO 可能刚把即将被重复访问的页换出,导致“抖动”式缺页。
实操建议:
- 不要直接用
std::queue模拟 FIFO 页面队列——它不支持 O(1) 查找是否命中,每次访问都要遍历判断,复杂度升到 O(n),会严重拖慢模拟速度 - 用
std::unordered_set维护当前驻留页集合,配合std::deque或std::list记录入队顺序;命中时只查 set,不修改顺序结构 - 注意:FIFO 的“先进先出”仅作用于 换入时的入队 和 缺页时的换出,命中不改变队列顺序——这点常被误实现为“命中后移到队尾”
LRU 模拟必须避免的三种链表误操作
手写双向链表或依赖 std::list 实现 LRU 时,最常见错误不是逻辑错,而是节点位置维护失效:
- 命中页未及时
splice或erase + push_back到链表尾部——导致该页在链表中位置滞后,下次可能被错误淘汰 - 换出页只从链表头删了节点,但没同步从
std::unordered_map(映射页号→迭代器)中 erase 对应项,后续再访问该页会触发 map 中悬空迭代器解引用,引发未定义行为 - 用
std::list::iterator作 map 的 value 时,插入新页后未更新 map 中旧迭代器——std::list迭代器在插入/删除时不失效,但std::vector或std::deque的迭代器会失效,选错容器就埋雷
如何生成有局部性的随机访问序列来公平对比
用纯均匀随机数(rand() % total_pages)测算法,LRU 优势会被掩盖,因为无局部性时两者表现接近。要暴露差异,需构造带时间局部性的序列:
- 推荐用“工作集模型”:固定窗口大小(如 20 页),每次以 85% 概率在当前窗口内随机选页,15% 概率跳到新窗口——这样能模拟程序阶段性访问某块内存的特征
- 避免用
std::rand():它周期短、低比特位随机性差,会导致页号分布不均;改用std::mt19937配std::uniform_int_distribution - 序列长度至少为页面数的 10 倍(例如 100 页 → 1000 次访问),否则冷启动效应主导结果,看不出稳定命中率差异
命中率统计与缓存大小影响的关键细节
命中率 = 命中次数 / 总访问次数,看似简单,但三个细节决定结果可信度:
- 缺页中断计数必须严格在“查页表未命中且需换入”时才加一;不能把“查页表命中但 TLB 未命中”算进去——模拟的是页表级置换,不是硬件缓存
- 测试不同缓存大小(如 4/8/16/32 页帧)时,务必对同一访问序列跑多轮(至少 3 次),因为随机序列本身有方差;取平均命中率而非单次值
- LRU 在缓存极小时(≤3 页帧)可能反超 FIFO 不明显,甚至略低——因频繁换入换出使链表维护开销占比升高;此时应关注 8 页帧及以上的趋势
真正容易被忽略的是:页面号是否从 0 开始连续?如果访问序列含非法页号(≥总页数),std::vector 下标访问会越界,而 std::unordered_map 可能静默插入默认值,导致命中率虚高。务必在模拟前加断言校验 access_page_id 。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











