FIFO 在真实访问序列下命中率常低于 LRU,因其忽略数据局部性,仅按装入顺序淘汰页面,而 LRU 保留最近使用页;实测需用真实 trace、跳过冷启动、正确实现数据结构与接口抽象。

为什么 FIFO 在真实访问序列下命中率常低于 LRU
FIFO 不看页面使用频次和时间远近,只按装入顺序淘汰,遇到“循环访问 + 页面数不足”场景(比如 4 页帧、访问序列 1,2,3,4,1,2,5,1,2,3,4,5),它会把刚用过的 1 淘汰掉,而 LRU 会保留最近用过的。这不是理论偏差,是数据局部性被完全忽略导致的实测差距。
实操建议:
- 用真实 trace(如 SPEC CPU 的 page reference trace)跑对比,别只用手写短序列——短序列可能偶然让 FIFO 占优,掩盖问题
- 统计命中率时,务必跳过前
page_frame_count次访问(冷启动阶段全是缺页),否则拉低 LRU 优势 - 注意:FIFO 的队列必须存
page_id而非指针或引用,避免因对象移动导致迭代器失效(尤其用std::deque时)
用 std::list + std::unordered_map 实现 O(1) LRU
手写链表+哈希是标准解法,但 C++ STL 的 std::list 迭代器不随插入/删除失效,配合 std::unordered_map 存迭代器,就能做到每次访问 O(1) 更新位置。
关键细节:
立即学习“C++免费学习笔记(深入)”;
-
std::unordered_map<int std::list>::iterator></int>中的iterator必须在list发生splice或push_front后重新获取——不能缓存旧迭代器 - 淘汰时调用
list.pop_back(),再用 map 删除对应键;插入新页时先list.push_front(id),再map[id] = list.begin() - 别用
std::vector模拟 LRU:erase中间元素是O(n),10 万次访问就明显卡顿
模拟器里怎么统一接口跑 FIFO 和 LRU 对比
别为每个算法写一套主循环。定义抽象基类 PageReplacementAlgo,含纯虚函数 access(int page_id) 和 get_hit_count(),让 FIFOAlgo 和 LRUAlgo 分别继承。
这样做的好处:
- 主逻辑只写一次:
for (int pg : trace) { algo->access(pg); } - 切换算法只需改构造:
std::unique_ptr<pagereplacementalgo> algo = std::make_unique<lrualgo>(frame_count);</lrualgo></pagereplacementalgo> - 加新算法(比如 Clock)时,不碰原有测试代码,符合开闭原则
- 注意:所有算法类内部必须用
std::vector<bool></bool>或std::bitset记录当前驻留页,否则access()里查是否命中要O(n)扫描
命中率数字不准?检查这三个内存模拟盲区
很多实现算出的命中率比预期高 5%–10%,问题往往不在算法逻辑,而在模拟假设失真:
- 没模拟 TLB:真实系统中 TLB 命中直接免查页表,你的“内存访问”其实该分两层——先查 TLB(快),再查页表(慢)。不拆开,命中率虚高
- 页大小硬编码为 4KB,但 trace 数据若来自 64KB 页系统(如某些 ARM 服务器),直接套用会导致页号计算错误——读 trace 时得按实际
page_size_log2位移取页号 - 多线程并发访问没加锁,但你的模拟器是单线程顺序处理 trace。这点没错,但若 trace 本身含多个地址空间(如 pid 隔离),你却全局共用一个页表,就会把不同进程的页冲突当成命中
真实系统里,页面置换不是孤立算法,它和 TLB、页表层级、进程地址空间绑定极深。模拟器越想逼近现实,越得先厘清 trace 的生成上下文。


















