test_ok2/py/misc/cache.py

155 lines
4.5 KiB
Python
Raw Normal View History

"""
This module contains multithread-safe cache implementations.
Caches mainly have a
__getitem__ and getorbuild() method
where the latter either just return a cached value or
first builds the value.
These are the current cache implementations:
BuildcostAccessCache tracks building-time and accesses. Evicts
by product of num-accesses * build-time.
"""
import py
gettime = py.std.time.time
class WeightedCountingEntry(object):
def __init__(self, value, oneweight):
self.num = 1
self._value = value
self.oneweight = oneweight
def weight():
def fget(self):
return (self.num * self.oneweight, self.num)
return property(fget, None, None, "cumulative weight")
weight = weight()
def value():
def fget(self):
# you need to protect against mt-access at caller side!
self.num += 1
return self._value
return property(fget, None, None)
value = value()
def __repr__(self):
return "<%s weight=%s>" % (self.__class__.__name__, self.weight)
class BasicCache(object):
def __init__(self, maxentries=128):
self.maxentries = maxentries
self.prunenum = int(maxentries - maxentries/8)
self._lock = py.std.threading.RLock()
self._dict = {}
def getentry(self, key):
lock = self._lock
lock.acquire()
try:
return self._dict.get(key, None)
finally:
lock.release()
def putentry(self, key, entry):
self._lock.acquire()
try:
self._prunelowestweight()
self._dict[key] = entry
finally:
self._lock.release()
def delentry(self, key, raising=False):
self._lock.acquire()
try:
try:
del self._dict[key]
except KeyError:
if raising:
raise
finally:
self._lock.release()
def getorbuild(self, key, builder, *args, **kwargs):
entry = self.getentry(key)
if entry is None:
entry = self.build(key, builder, *args, **kwargs)
return entry.value
def _prunelowestweight(self):
""" prune out entries with lowest weight. """
# note: must be called with acquired self._lock!
numentries = len(self._dict)
if numentries >= self.maxentries:
# evict according to entry's weight
items = [(entry.weight, key) for key, entry in self._dict.iteritems()]
items.sort()
index = numentries - self.prunenum
if index > 0:
for weight, key in items[:index]:
del self._dict[key]
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
_time = gettime
def build(self, key, builder, *args, **kwargs):
start = self._time()
val = builder(*args, **kwargs)
end = self._time()
entry = WeightedCountingEntry(val, end-start)
self.putentry(key, entry)
return entry
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):
self._lock.acquire()
try:
try:
entry = self._dict[key]
except KeyError:
entry = None
else:
if entry.isexpired():
del self._dict[key]
entry = None
return entry
finally:
self._lock.release()
def build(self, key, builder, *args, **kwargs):
ctime = gettime()
val = builder(*args, **kwargs)
entry = AgingEntry(val, ctime + self.maxseconds)
self.putentry(key, entry)
return entry
class AgingEntry(object):
def __init__(self, value, expirationtime):
self.value = value
self.weight = expirationtime
def isexpired(self):
t = py.std.time.time()
return t >= self.weight