cacheutil.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. """
  2. This module contains multithread-safe cache implementations.
  3. All Caches have
  4. getorbuild(key, builder)
  5. delentry(key)
  6. methods and allow configuration when instantiating the cache class.
  7. """
  8. from time import time as gettime
  9. class BasicCache(object):
  10. def __init__(self, maxentries=128):
  11. self.maxentries = maxentries
  12. self.prunenum = int(maxentries - maxentries/8)
  13. self._dict = {}
  14. def clear(self):
  15. self._dict.clear()
  16. def _getentry(self, key):
  17. return self._dict[key]
  18. def _putentry(self, key, entry):
  19. self._prunelowestweight()
  20. self._dict[key] = entry
  21. def delentry(self, key, raising=False):
  22. try:
  23. del self._dict[key]
  24. except KeyError:
  25. if raising:
  26. raise
  27. def getorbuild(self, key, builder):
  28. try:
  29. entry = self._getentry(key)
  30. except KeyError:
  31. entry = self._build(key, builder)
  32. self._putentry(key, entry)
  33. return entry.value
  34. def _prunelowestweight(self):
  35. """ prune out entries with lowest weight. """
  36. numentries = len(self._dict)
  37. if numentries >= self.maxentries:
  38. # evict according to entry's weight
  39. items = [(entry.weight, key)
  40. for key, entry in self._dict.items()]
  41. items.sort()
  42. index = numentries - self.prunenum
  43. if index > 0:
  44. for weight, key in items[:index]:
  45. # in MT situations the element might be gone
  46. self.delentry(key, raising=False)
  47. class BuildcostAccessCache(BasicCache):
  48. """ A BuildTime/Access-counting cache implementation.
  49. the weight of a value is computed as the product of
  50. num-accesses-of-a-value * time-to-build-the-value
  51. The values with the least such weights are evicted
  52. if the cache maxentries threshold is superceded.
  53. For implementation flexibility more than one object
  54. might be evicted at a time.
  55. """
  56. # time function to use for measuring build-times
  57. def _build(self, key, builder):
  58. start = gettime()
  59. val = builder()
  60. end = gettime()
  61. return WeightedCountingEntry(val, end-start)
  62. class WeightedCountingEntry(object):
  63. def __init__(self, value, oneweight):
  64. self._value = value
  65. self.weight = self._oneweight = oneweight
  66. def value(self):
  67. self.weight += self._oneweight
  68. return self._value
  69. value = property(value)
  70. class AgingCache(BasicCache):
  71. """ This cache prunes out cache entries that are too old.
  72. """
  73. def __init__(self, maxentries=128, maxseconds=10.0):
  74. super(AgingCache, self).__init__(maxentries)
  75. self.maxseconds = maxseconds
  76. def _getentry(self, key):
  77. entry = self._dict[key]
  78. if entry.isexpired():
  79. self.delentry(key)
  80. raise KeyError(key)
  81. return entry
  82. def _build(self, key, builder):
  83. val = builder()
  84. entry = AgingEntry(val, gettime() + self.maxseconds)
  85. return entry
  86. class AgingEntry(object):
  87. def __init__(self, value, expirationtime):
  88. self.value = value
  89. self.weight = expirationtime
  90. def isexpired(self):
  91. t = gettime()
  92. return t >= self.weight