我正在尝试使用C ++实现LRU Cache。我想知道实现它们的最佳设计是什么。我知道LRU应该提供find(),添加一个元素并删除一个元素。删除应删除LRU元素。什么是最好的ADT来实现这一点 例如:如果我使用带有元素值的映射作为值,时间计数器作为键,我可以在O(logn)时间内搜索,Inserting为O(n),删除为O(logn)。
答案 0 :(得分:14)
LRU缓存的一个主要问题是几乎没有“const”操作,大多数会更改底层表示(如果只是因为它们会碰到访问的元素)。
这当然非常不方便,因为它意味着它不是传统的STL容器,因此任何展示迭代器的想法都非常复杂:当解除引用迭代器时,这是一个访问,它应该修改我们迭代的列表......哦,我的。
在速度和内存消耗方面都有性能考虑因素。
很不幸,但是您需要一些方法来组织队列中的数据(LRU)(可以从中间删除元素),这意味着您的元素必须彼此独立。当然,std::list
适合,但它超出了你的需要。这里有一个单链表就足够了,因为你不需要向后迭代列表(毕竟你只想要一个队列)。
然而,其中一个主要缺点是它们的引用局部性较差,如果你需要更高的速度,你需要为节点提供自己的自定义(池?)分配器,以便它们尽可能保持紧密。这也会在一定程度上缓解堆碎。
接下来,您显然需要一个索引结构(用于缓存位)。最自然的是转向哈希映射。 std::tr1::unordered_map
,std::unordered_map
或boost::unordered_map
通常都是高质量的实施,有些应该可供您使用。他们还为哈希冲突处理分配额外的节点,您可能更喜欢其他类型的哈希映射,查看关于主题的Wikipedia's article并阅读各种实现技术的特征。
继续,有(明显的)线程支持。如果你不需要线程支持,那么没关系,如果你这样做,它会有点复杂:
const
,因此您不需要区分读/写访问std::unique_ptr<Lock> lock()
方法(在调试中,您可以断言,而不是在每个方法的入口点处进行锁定)最后,存在错误报告问题。由于预计缓存可能无法检索您输入的数据,因此我会考虑使用“不良品味”的例外情况。考虑指针(Value*
)或Boost.Optional(boost::optional<Value&>
)。我更喜欢Boost.Optional,因为它的语义清晰。
答案 1 :(得分:7)
实现LRU的最佳方法是使用std :: list和stdext :: hash_map的组合(只想使用std然后std :: map)。
这是你能得到的最快的,如果你使用的是hash_map,你几乎应该在O(1)中完成所有的操作。如果使用std :: map,它应该在所有情况下都需要O(logn)。
可以使用非常好的实施here
答案 2 :(得分:5)
这个article描述了几个C ++ LRU缓存实现(一个使用STL,一个使用boost::bimap
)。
答案 3 :(得分:2)
当您说优先级时,我认为“heap”自然会导致增加键和 delete-min 。
答案 4 :(得分:2)
如果我可以避免,我根本不会让缓存对外界可见。我只是有一个集合(无论如何)并且无形地处理缓存,根据需要添加和删除项目,但外部接口将与底层集合完全相同。
就实现而言,堆可能是最明显的。它具有与地图大致相似的复杂性,但它不是从链接节点构建树,而是在数组中排列项,而“链接”是基于数组索引隐式的。这会增加缓存的存储密度,会改善“真实”(物理)处理器缓存中的位置。
答案 5 :(得分:0)
我建议heap,也许Fibonacci Heap
答案 6 :(得分:0)
我会使用C ++中的普通堆。
使用std :: make_heap(标准保证为O(n)),std :: pop_heap和#include中的std :: push_heap,实现它绝对是蛋糕。你只需要担心增加密钥。