最近最少使用(LRU)缓存

时间:2017-08-09 07:33:54

标签: c++ c algorithm data-structures

我知道我可以在STL中使用各种容器类,但对于此目的而言,这是一种过度和昂贵的东西。

我们有超过1M +用户在线,每个用户需要维护8个不相关的32位数据项。目标是

  1. 查找列表中是否存在项目
  2. 如果没有,请插入。如果已满,请删除最旧的条
  3. 蛮力方法是维持最后一个写指针并迭代(因为只有8个项目),但我正在寻找更好的分析和实现的输入。

    期待在设计模式和算法方面提出一些有趣的建议。

6 个答案:

答案 0 :(得分:2)

Don Knuth在计算机程序设计的艺术中提供了几个有趣且非常有效的近似值。

  1. 自组织列表I:当您找到一个条目时,将其移到列表的头部;从最后删除。
  2. 自组织清单II:当您找到一个条目时,将其向上移动一个位置;从最后删除。

    [以上都在Vol。 3§6.1(A)。]

  3. 另一种方案涉及循环维护列表,每个条目一个额外位,当您找到该条目时设置,并在您跳过它以查找其他内容时清除。你总是在你停下来的最后一个地方开始搜索,如果你没找到这个条目你用它替换下一个清除位的条目,也就是说它已经没有被使用,因为整个行程都在列表中。

    [卷1§2.5(G)。]

答案 1 :(得分:1)

您想在此处使用Hash表和doubly linked list的组合 每个项目都可以通过哈希表访问,哈希表包含您需要的键以及指向列表中元素的指针。

<强>算法:

鉴于新项目x,请执行:
1.将x添加到列表的头部,将指针保存为ptr 2.将x添加到存储数据的哈希表中,然后添加ptr 3.如果列表大于允许值,则取最后一个元素(从列表尾部开始)并将其删除。使用此元素的键也可以将其从哈希表中删除。

答案 2 :(得分:1)

如果您想要LRU缓存的C实现,请尝试此link

我们的想法是使用两个数据结构来实现LRU Cache。

使用双向链表实现的队列。队列的最大大小将等于可用的总帧数(高速缓存大小)。最近使用的页面将接近前端,最近最少的页面将接近后端。
一个Hash,页码作为键,相应队列节点的地址作为值。
引用页面时,所需页面可能位于内存中。如果它在内存中,我们需要分离列表的节点并将其带到队列的前面。
如果所需页面不在内存中,我们将其带入内存。简单来说,我们在队列的前面添加一个新节点,并更新哈希中相应的节点地址。如果队列已满,即所有帧都已满,我们从队列后面删除一个节点,并将新节点添加到队列的前面。

答案 3 :(得分:0)

您应该使用Cuckoo's Filter这是支持快速set成员资格测试的概率数据结构。它是基于hash的数据结构。

杜鹃过滤器的时间复杂度:

  1. 查找: O(1)
  2. 删除: O(1)
  3. 插入: O(1)
  4. 此处参考布谷鸟过滤器的工作原理。

    Parameters of the filter
    
     1. Two Hash Functions: h1 and h2
     2. An array B with n Buckets. The i-th Bucket will be called B[i]
    
    Input : L, a list of elements to inserted into the cuckoo filter.
    
    Algorithm:
    while L is not empty:
        Let x be the 1st item in the list L. Remove x from the list.
        if (B[h1(x)] == empty)
              place x in B[h1(x)];
        else if (B[h2(x)] == empty)
              place x in B[h2(x)];
        else
              Let y be the element in B[h2(x)]
              Prepend y to L
              place x in B[h2(x)]
    

    对于LRU,您可以通过仅保留局部变量在哈希函数中使用时间戳。

    这是迄今为止非常大的数据集的最佳方法。

答案 4 :(得分:0)

我个人会选择EJP提出的自组织列表,或者因为我们只有8个元素,所以只需将它们与时间戳顺序存储在一起。

访问元素时,只需更新时间戳,替换时,用最旧的时间戳替换一个(一次线性搜索)。这在替换方面效率较低,但访问效率更高(无需移动任何元素)。它可能是最容易实现的......

自组织列表的修改,如果基于某些数组数据结构:当然,在更新时,您必须移动几个元素(变体I)或至少交换其中两个元素(变体II) - 但是如果您组织数据作为环形缓冲区,在替换时我们只用新的元素替换最后一个元素并将缓冲区的指针移动到这个新元素:

a, b, c, d
      ^

访问:

d, b, a, c
      ^

新元素e:

d, e, a, c
   ^

特殊情况:访问最旧的元素(在本例中为d) - 然后我们也可以移动指针:

d, e, a, c
^

只是:只有8个元素,实现所有这些可能不值得......

答案 5 :(得分:0)

我同意Drop和Geza的评论。直接实现将读取一个高速缓存行,并导致一个高速缓存行写入。

剩下的唯一性能问题是以256位查找和更新该32位值。假设现代x86,查找本身可以是两个指令:_mm256_cmp_epi32_mask并行查找所有相等的值,_mm256_lzcnt_epi32计数前导零=较旧的非匹配项目数* 32。但即使使用较旧的SIMD操作,高速缓存行读/写操作也将占据执行时间。反过来,这又是找到合适的用户所主导的。而这又受到网络I / O的支配。

相关问题