代码之家  ›  专栏  ›  技术社区  ›  max

对于小型和简单的缓存,什么是最好的数据结构

  •  2
  • max  · 技术社区  · 15 年前

    我经常需要相对较小的(10000个条目<1KB)缓存来加快计算速度。我通常的代码如下:

    cache = {}
    def calculate_caches(parms):
        if parms not in cache:
            cache[parms] = calculate(parms)
        return cache[parms]
    

    工作正常,但对于长时间运行的进程,我担心内存泄漏。所以我经常像这样执行强力内存夹持:

    if len(cache) > 1000:
        cache = {}
    

    在大多数情况下都能很好地工作,并且仍然是干净、简单的代码。但如果我想要一个真正的 LRU strategy 我需要时间戳和缓存项。为此使用dict的问题是,使缓存过期现在意味着遍历整个缓存,这既不优雅也不高效。

    cache = {}
    def calculate_caches(parms):
        if parms not in cache:
            cache[parms] = (time.time(), calculate(parms))
        expire()
        return cache[parms][1]
    
    def expire()
        if len(cache) > 1000:
            mintime = time.time()
            time2key = {}
            for key, (timestamp, val) in cache.items():
                mintime = min([mintime, timestamp])
                time2key[timestamp] = key
            if mintime in time2key:
                del cache[time2key[mintime]]
    

    是否有更好的方法/数据结构来实现即席缓存?

    我的问题和 this question 但我不需要按时间排序,我也不想被骗。

    3 回复  |  直到 7 年前
        1
  •  1
  •   Vinko Vrsalovic    15 年前

    不使用时间戳的一个非常简单的方法是 ordered dictionary ,其中您在结尾处有MRU(即,当第二次请求同一对象时,您将其删除并在dict结尾处再次添加),因此,当您需要过期时,如果大小大于限制,您只需从已排序dict的开头删除一个大小为X的切片。

    效率现在取决于如何实现有序的dict。

        2
  •  1
  •   shailendra1118    7 年前

    你可以看看Java的 LinkedHashMap 和Linkedhashset的灵感。基本上,它为插入和可选的访问顺序维护一个双重LinkedList。

    要维护LRU,可以定义在插入新条目时删除最旧条目(靠近LinkedList头部)的策略。

        3
  •  0
  •   othercriteria    15 年前

    我怀疑这有一个黄金子弹;最佳策略很大程度上取决于缓存未命中的成本和计算参数的时间分布。

    垃圾收集方法可能会给你一些启发。如果您将缓存视为堆,而缓存命中视为引用,那么就有了高效收集缓存结果的问题。 低的 (不是零)点击次数。这个问题比GC更容易理解,因为任何你核的东西都可以重新计算。

    在这种情况下,对方法的改进是为频繁命中的参数引入一个额外的缓存。向缓存命中时递增的每个缓存值添加计数器。当通过某个阈值时,缓存值将提升为其他缓存。两代缓存都可以进行大小钳制,所以对内存使用仍然有一个硬限制。这是一个经验问题,如果(可能的)减少缓存未命中可以证明开销(在两个缓存中查找、命中计数器、复制等)是合理的……