Проблемы с GC при использовании WeakValueDictionary для кэшей
Согласно официальной документации Python для модуля слабой ссылки, "основное использование слабых ссылок заключается в реализации кэшей или отображений, содержащих большие объекты,...". Итак, я использовал WeakValueDictionary для реализации механизма кэширования для долго выполняющейся функции. Однако, как выяснилось, значения в кеше никогда не оставались там до тех пор, пока они фактически не будут использованы снова, а требовалось пересчитывать почти каждый раз. Поскольку не было строгих ссылок между доступом к значениям, хранящимся в WeakValueDictionary, GC избавился от них (даже при том, что не было абсолютно никаких проблем с памятью).
Теперь, как мне тогда использовать слабые ссылки для реализации кеша? Если я где-то явно храню сильные ссылки, чтобы GC не удалял мои слабые ссылки, было бы бессмысленно использовать WeakValueDictionary в первую очередь. Вероятно, должна быть какая-то опция для GC, которая говорит ему: удаляйте все, что не имеет ссылок вообще, и все со слабыми ссылками только тогда, когда заканчивается память (или превышен некоторый порог). Есть ли что-то подобное? Или есть лучшая стратегия для этого типа кэша?
2 ответа
Я попытаюсь ответить на ваш запрос с примером того, как использовать 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
удалит эти ключи с карты. Следовательно, сочетание двух объектов помогает нам сохранить только maxlen
записи в нашем кеше LRU.
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)
Похоже, что нет способа преодолеть это ограничение, по крайней мере, в CPython 2.7 и 3.0.
Размышляя о решении createLRUCache():
Решение с createLRUCache(factory, maxlen=64) не соответствует моим ожиданиям. Идею привязки к 'maxlen' я бы хотел избежать. Это заставило бы меня указать здесь некоторую немасштабируемую константу или создать некоторую эвристику, чтобы решить, какая константа лучше для тех или иных пределов памяти хоста.
Я бы предпочел, чтобы GC удалял несвязанные значения из WeakValueDictionary не сразу, но при условии используется для обычного GC:
Когда количество выделений минус количество освобождений превышает порог 0, сбор начинается.