商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > C++实现简单的LRU-K算法 _ 解决偶发性访问热点问题【源码】

C++实现简单的LRU-K算法 _ 解决偶发性访问热点问题【源码】

  发布于2026-07-18 阅读(0)

扫一扫,手机访问

咱们先直说了吧:std::list + std::unordered_map 这套组合拳,打不了 LRU-K。哪怕 K 只取 2,只要你还想着遍历链表去更新访问记录,那缓存驱逐的延迟就会随着访问频率一路走高,最后彻底崩盘。

C++实现简单的LRU-K算法 _ 解决偶发性访问热点问题【源码】

为什么 std::list 不适合 LRU-K 的第 K 次访问时间维护

LRU-K 的核心要求,是对每个 key 维护最近 K 次访问的精确时间戳。淘汰的时候,它看的不是最新那次访问,而是倒数第 K 次——也就是“第 K 次访问时间”。而 std::list 呢?它只擅长 O(1) 地把节点挪到头尾,却没法 O(1) 地定位并修改中间某次访问记录。

常见错误有哪些?

  • 有人用链表节点存 K 个时间戳,结果每次插入新时间都得遍历去找最老的位置,平均复杂度 O(K)。K=4 时,就是 4 次指针跳转加内存访问,性能肉眼可见地往下掉。
  • 有人每次 get 都重排整个链表,按第 K 次时间排序。虽然可能留到淘汰前才真算,但 get 操作一多,隐式的重排逻辑就会被触发。实测下来,QPS 能掉 30% 以上。
  • 最典型的一个坑,是把 LRU-1 的 head/tail 模式直接套到 LRU-K 上,结果“刚被访问 K 次”的热数据,反而比“只访问了 K-1 次但沉寂很久”的冷数据更早被淘汰。这逻辑一跑,缓存命中率直接翻车。

用环形数组 + 单调 tick 实现第 K 次访问时间推导

核心思路就一条:别实时排序。每个 key 只存一个 std::array 和一个游标 size_t cursor。新访问进来,覆盖掉最老的那次记录。第 K 次访问时间,就是 access_ticks[(cursor - K + 1) % K]。注意负数取模在 C++ 里是未定义行为,所以计算时一定先加上 K。

实操建议:

  • tick 用全局原子递增的 uint64_t,别依赖系统时间。系统时间可能回拨,精度也没保障。
  • 环形数组索引必须用 size_t,取模时写成 (cursor + K - 1) % K,别直接 cursor - K + 1 取模。
  • K 值建议硬编码为 2 或 3。K=4 时环形数组空间翻倍,但命中率提升还不到 0.7%,反而 cache line miss 会上升 12%,得不偿失。
  • 时间戳和 value 别放在同一个结构体里。热数据的 page buffer 和访问历史必须分离,否则驱逐时没法原子释放内存,容易出问题。

淘汰时不扫全量,改用热度桶分层采样

实时维护一个全局有序队列,代价太高,不值得。正确的做法是:等到需要驱逐时,才对候选 key 集合批量计算 access_ticks[(cursor - K + 1) % K],然后映射到离散热度桶。桶的划分可以参考 0–50ms、50–200ms、200–1s、>1s 这样的区间。

关键细节:

  • 桶用 std::vector> 实现,每个桶内的 key 按插入顺序排列,不排序。
  • 淘汰时只查最冷的 1–2 个桶。如果桶为空,就向前合并相邻的桶,别让空桶卡死驱逐流程。
  • 每 100 次 insert 后触发一次桶重组:遍历所有桶,把 now_tick - access_time > 1s 的 key 移到新桶,防止冷数据永久滞留。
  • 桶数量别超过 8。实测 16 桶时,hash 分布会严重倾斜,30% 的 key 挤在前两个桶,淘汰效率反而下降。

说实话,写对逻辑本身并不难。真正棘手的地方,在于控制 tick 更新和桶重组的时机——它们必须和缓冲池的 page pin/unpin、脏页刷盘节奏对齐。否则就会出现“刚标记为冷数据,下一秒就被事务强制 pin 住”的竞态问题。这一层耦合,在很多开源实现里都被忽略了。

本文转载于:https://www.php.cn/faq/2340847.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注