1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
|
"""
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 __init__(self, maxentries=64):
super(BuildcostAccessCache, self).__init__(maxentries)
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
|