| """ |
| This module contains multithread-safe cache implementations. |
| |
| All Caches have |
| |
| getorbuild(key, builder) |
| delentry(key) |
| |
| methods and allow configuration when instantiating the cache class. |
| """ |
| from time import time as gettime |
| |
| class BasicCache(object): |
| def __init__(self, maxentries=128): |
| self.maxentries = maxentries |
| self.prunenum = int(maxentries - maxentries/8) |
| self._dict = {} |
| |
| def clear(self): |
| self._dict.clear() |
| |
| def _getentry(self, key): |
| return self._dict[key] |
| |
| def _putentry(self, key, entry): |
| self._prunelowestweight() |
| self._dict[key] = entry |
| |
| def delentry(self, key, raising=False): |
| try: |
| del self._dict[key] |
| except KeyError: |
| if raising: |
| raise |
| |
| def getorbuild(self, key, builder): |
| try: |
| entry = self._getentry(key) |
| except KeyError: |
| entry = self._build(key, builder) |
| self._putentry(key, entry) |
| return entry.value |
| |
| def _prunelowestweight(self): |
| """ prune out entries with lowest weight. """ |
| numentries = len(self._dict) |
| if numentries >= self.maxentries: |
| # evict according to entry's weight |
| items = [(entry.weight, key) |
| for key, entry in self._dict.items()] |
| items.sort() |
| index = numentries - self.prunenum |
| if index > 0: |
| for weight, key in items[:index]: |
| # in MT situations the element might be gone |
| self.delentry(key, raising=False) |
| |
| class BuildcostAccessCache(BasicCache): |
| """ A BuildTime/Access-counting cache implementation. |
| the weight of a value is computed as the product of |
| |
| num-accesses-of-a-value * time-to-build-the-value |
| |
| The values with the least such weights are evicted |
| if the cache maxentries threshold is superceded. |
| For implementation flexibility more than one object |
| might be evicted at a time. |
| """ |
| # time function to use for measuring build-times |
| |
| def _build(self, key, builder): |
| start = gettime() |
| val = builder() |
| end = gettime() |
| return WeightedCountingEntry(val, end-start) |
| |
| |
| class WeightedCountingEntry(object): |
| def __init__(self, value, oneweight): |
| self._value = value |
| self.weight = self._oneweight = oneweight |
| |
| def value(self): |
| self.weight += self._oneweight |
| return self._value |
| value = property(value) |
| |
| class AgingCache(BasicCache): |
| """ This cache prunes out cache entries that are too old. |
| """ |
| def __init__(self, maxentries=128, maxseconds=10.0): |
| super(AgingCache, self).__init__(maxentries) |
| self.maxseconds = maxseconds |
| |
| def _getentry(self, key): |
| entry = self._dict[key] |
| if entry.isexpired(): |
| self.delentry(key) |
| raise KeyError(key) |
| return entry |
| |
| def _build(self, key, builder): |
| val = builder() |
| entry = AgingEntry(val, gettime() + self.maxseconds) |
| return entry |
| |
| class AgingEntry(object): |
| def __init__(self, value, expirationtime): |
| self.value = value |
| self.weight = expirationtime |
| |
| def isexpired(self): |
| t = gettime() |
| return t >= self.weight |