【问题标题】:Least Recently Used cache using C++使用 C++ 的最近最少使用缓存
【发布时间】:2011-04-08 01:45:02
【问题描述】:

我正在尝试使用 C++ 实现 LRU 缓存。我想知道实现它们的最佳设计是什么。我知道 LRU 应该提供 find()、添加元素和删除元素。删除应该删除 LRU 元素。实现这一点的最佳 ADT 是什么 例如:如果我使用元素作为值和时间计数器作为键的映射,我可以在 O(logn) 时间内搜索,插入是 O(n),删除是 O(logn)。

【问题讨论】:

标签: c++ algorithm data-structures lru


【解决方案1】:

LRU 缓存的一个主要问题是几乎没有“const”操作,大多数会更改底层表示(如果只是因为它们会影响访问的元素)。

这当然很不方便,因为这意味着它不是传统的 STL 容器,因此任何展示迭代器的想法都非常复杂:当迭代器被取消引用时,这是一个访问,它应该修改我们正在迭代的列表...哦,我的。

还有性能方面的考虑,包括速度和内存消耗。

不幸的是,您需要某种方式将数据组织到队列 (LRU) 中(可以从中间删除元素),这意味着您的元素必须彼此独立。当然,std::list 很合适,但这超出了您的需要。单链表在这里就足够了,因为您不需要向后迭代列表(毕竟您只需要一个队列)。

然而,其中一个主要缺点是它们的引用局部性较差,如果您需要更快的速度,则需要为节点提供自己的自定义(池?)分配器,以便它们尽可能靠近。这也将在一定程度上缓解堆碎片。

接下来,您显然需要一个索引结构(用于缓存位)。最自然的是转向哈希图。 std::tr1::unordered_mapstd::unordered_mapboost::unordered_map 通常是高质量的实现,您应该可以使用一些。他们还为哈希冲突处理分配了额外的节点,您可能更喜欢其他类型的哈希映射,请查看该主题的 Wikipedia's article 并阅读各种实现技术的特征。

继续,有(明显的)线程支持。如果你不需要线程支持,那很好,但是如果你需要,那就有点复杂了:

  • 正如我所说,在这种结构上几乎没有 const 操作,因此您实际上不需要区分读/写访问
  • 内部锁定很好,但您可能会发现它不适合您的使用。内部锁定的问题在于它不支持“事务”的概念,因为它在每次调用之间放弃了锁定。如果是这种情况,请将您的对象转换为互斥体并提供 std::unique_ptr<Lock> lock() 方法(在调试中,您可以断言在每个方法的入口点获取锁)
  • (在锁定策略中)存在重入问题,即从同一线程中“重新锁定”互斥锁的能力,查看 Boost.Thread 以获取有关可用的各种锁和互斥锁的更多信息

最后,还有报错的问题。由于预计缓存可能无法检索您放入的数据,因此我会考虑使用“不良品味”异常。考虑指针 (Value*) 或 Boost.Optional (boost::optional<Value&>)。我更喜欢 Boost.Optional 因为它的语义很清楚。

【讨论】:

    【解决方案2】:

    实现 LRU 的最佳方式是使用 std::list 和 stdext::hash_map 的组合(希望只使用 std 然后使用 std::map)。

    • 将数据存储在列表中,以便 最近最少使用的 最后并使用地图指向 列出项目。
    • 对于“获取”,使用地图获取 列出地址并检索数据 并将当前节点移动到
      首先(因为现在使用它)并更新地图。
    • 对于“插入”,删除最后一个元素 从列表中添加新数据 到前面并更新地图。

    这是你能得到的最快速度,如果你使用 hash_map,你应该几乎可以在 O(1) 中完成所有操作。如果使用 std::map 它应该在所有情况下都需要 O(logn) 。

    一个非常好的实现是可用的here

    【讨论】:

    • 我看了lru_cache的代码。它基于std::map,这意味着O(n log n) 复杂性。
    • @Matthieu:它不是 O(nlogn) 而是 O(logn),因为根据 stl doc,每个插入/擦除/获取都是 O(logn)。然而,只需用适当的散列函数将 std::map 替换为 stdext::hash_map 肯定会让你得到 O(1)
    • 是的,抱歉,这里有点跑题了。 O(log n) 当然。我们都同意哈希映射可能会更好。
    【解决方案3】:

    article 描述了几个 C++ LRU 缓存实现(一个使用 STL,一个使用 boost::bimap)。

    【讨论】:

      【解决方案4】:

      当您说优先级时,我认为“heap”自然会导致 increase-keydelete-min

      【讨论】:

        【解决方案5】:

        如果可以避免的话,我根本不会让外部世界看到缓存。我只需要一个集合(不管是什么)并以不可见的方式处理缓存,根据需要添加和删除项目,但外部接口将完全是底层集合的接口。

        就实现而言,堆可能是最明显的。它的复杂性与地图大致相似,但不是从链接节点构建树,而是将项目排列在数组中,并且“链接”是基于数组索引的隐式。这会增加缓存的存储密度改善“真实”(物理)处理器缓存中的局部性。

        【讨论】:

          【解决方案6】:

          我建议heap 或者Fibonacci Heap

          【讨论】:

            【解决方案7】:

            我会使用 C++ 中的普通堆。

            使用#include 中的std::make_heap(标准保证为O(n))、std::pop_heap 和std::push_heap,实现它绝对是小菜一碟。你只需要担心增加键。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-01-09
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多