Проблемы с GC при использовании WeakValueDictionary для кэшей - PullRequest
5 голосов
/ 13 марта 2012

В соответствии с официальной документацией Python для модуля слабой ссылки «основное использование слабых ссылок заключается в реализации кэшей или отображений, содержащих большие объекты, ...». Итак, я использовал WeakValueDictionary для реализации механизма кэширования для долго выполняющейся функции. Однако, как выяснилось, значения в кеше никогда не оставались там до тех пор, пока они фактически не будут использованы снова, а требовалось пересчитывать почти каждый раз. Поскольку не было строгих ссылок между доступом к значениям, хранящимся в WeakValueDictionary, GC избавился от них (даже при том, что не было абсолютно никаких проблем с памятью).

Теперь, как мне тогда использовать слабые ссылки для реализации кеша? Если я где-то явно храню сильные ссылки, чтобы GC не удалял мои слабые ссылки, не было бы смысла использовать WeakValueDictionary в первую очередь. Вероятно, должна быть какая-то опция для GC, которая говорит ему: удаляйте все, у которых вообще нет ссылок, и все со слабыми ссылками, только когда память заканчивается (или превышен некоторый порог). Есть ли что-то подобное? Или есть лучшая стратегия для такого типа кэша?

Ответы [ 2 ]

3 голосов
/ 14 марта 2012

Я попытаюсь ответить на ваш запрос с примером того, как использовать модуль 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)
1 голос
/ 02 декабря 2014

Похоже, нет способа обойти это ограничение, по крайней мере, в CPython 2.7 и 3.0.

Размышления о решении createLRUCache ():

Решение с помощью createLRUCache (factory, maxlen)= 64) не соответствует моим ожиданиям.Идею привязки к 'maxlen' я бы хотел избежать.Это заставило бы меня указать здесь некоторую немасштабируемую константу или создать некоторую эвристику, чтобы решить, какая константа лучше для того или иного предела памяти хоста.

Я бы предпочел, чтобы GC удаляла не связанные значения из WeakValueDictionary не сразу,но при условие используется для обычного GC :

Когда количество выделений минус число освобождений превышает порог 0, сбор начинается.

...