使用WeakValueDictionary作为缓存时,GC存在问题

7
根据Python官方文档中的弱引用模块,“弱引用的主要用途是实现缓存或映射大型对象……”。因此,我使用了WeakValueDictionary来为长时间运行的函数实现缓存机制。但事实证明,缓存中的值几乎每次都需要重新计算,而不是等到下次实际使用时再从缓存中取出。由于在访问WeakValueDictionary中存储的值之间没有强引用,垃圾回收器将其清除(尽管内存完全没有问题)。
那么,我应该如何使用弱引用来实现缓存呢?如果我明确地保留了某处的强引用以防止GC删除我的弱引用,那么使用WeakValueDictionary就毫无意义了。可能应该有一些选项告诉GC:删除所有没有任何引用和仅具有弱引用的内容,只有在内存不足或超过某个阈值时才这样做。是否有这样的东西?或者是否有更好的策略来处理这种缓存?
2个回答

5
我会尝试用一个例子来回答你的问题,展示如何使用`weakref`模块来实现缓存。我们将在一个`weakref.WeakValueDictionary`中保存缓存的弱引用,并在一个`collections.deque`中保存强引用,因为它有一个`maxlen`属性来控制它所持有的对象数量。以下是函数闭包风格的实现:
import weakref, collections
def createLRUCache(factory, maxlen=64):
    weak = weakref.WeakValueDictionary()
    strong = collections.deque(maxlen=maxlen)

    notFound = object()
    def fetch(key):
        value = weak.get(key, notFound)
        if value is notFound:
            weak[key] = value = factory(key)
        strong.append(value)
        return value
    return fetch

deque 对象只会保留最近的 maxlen 个条目,一旦达到容量,就会简单地丢弃对旧条目的引用。当旧条目被 python 删除并进行垃圾回收时,WeakValueDictionary 将从映射中删除这些键。因此,两个对象的结合帮助我们在 LRU 缓存中仅保留 maxlen 个条目。

class Silly(object):
    def __init__(self, v):
        self.v = v

def fib(i):
    if i > 1:
        return Silly(_fibCache(i-1).v + _fibCache(i-2).v)
    elif i: return Silly(1)
    else: return Silly(0)
_fibCache = createLRUCache(fib)

1

看起来在CPython 2.7和3.0中至少没有克服这个限制的方法。

反思createLRUCache()解决方案:

使用createLRUCache(factory, maxlen=64)的解决方案不符合我的期望。绑定到'maxlen'的想法是我想避免的。它会迫使我在此处指定一些不可扩展的常量或创建一些启发式算法,以决定哪个常量更适合于这个或那个主机内存限制。

我希望GC不会立即从WeakValueDictionary中消除未引用的值,而是在用于常规GC的条件下进行消除:

当分配数量减去销毁数量超过threshold0时,开始收集。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接