Skip to content

Instantly share code, notes, and snippets.

@zizon
Created April 12, 2017 10:14
Show Gist options
  • Save zizon/f241fcc323d9051a75376e101cbb983c to your computer and use it in GitHub Desktop.
Save zizon/f241fcc323d9051a75376e101cbb983c to your computer and use it in GitHub Desktop.
from functools import wraps
class Caches(object):
_all_caches = {}
def __init__(self,name,size):
super(Caches,self).__init__()
if size is None:
size = 10000
self._name = name
self._containers = {}
self._size = size
self._current = 0
# for stat
self._total_hit = 0
self._total_miss = 0
# eviction
self._period = self._size / 10 + 1
self._invoke = 0
# disable log
self._nolog = False
# generation
self._generation = 0
@staticmethod
def fetch(name,size=None):
key = '%s' % (name)
hit = Caches._all_caches.get(key,None)
if hit is None:
Caches._all_caches[key] = hit = Caches(name,size)
return hit
def nolog(self,flag=True):
self._nolog = flag
return self._nolog
def clear(self):
self._containers.clear()
def limit(self,size):
self._size = size
self.evict()
def evict(self):
if self._current >= self._size:
self.shrink(True)
return True
return False
def shrink(self,ignore_generation = False):
if not self._nolog:
logging.info('trigger a eviction:%s,current:%s size:%s...' % (
self._name,
self._current,
self._size
)
)
# save counter
save = self._current
generation = self._generation
# do eviction
for i in xrange(self._current/2):
key,value = self._containers.popitem()
self._current -= 1
if not ignore_generation and value['generation'] == generation:
# skip new item
self._containers[key] = value
self._current += 1
continue
value['access'] = int(value['access'] / 2)
if value['access'] > 0:
self._containers[key] = value
self._current += 1
# increase generation
self._generation += 1
if not self._nolog:
logging.info('evict done:%s,current:%s size:%s evict:%s hit:%s miss:%s ratio:%s generation:%s' % (
self._name,
self._current,
self._size,
save - self._current,
self._total_hit,
self._total_miss,
self._total_hit * 1.0 / (self._total_hit + self._total_miss),
self._generation,
)
)
# reset counter
self._total_hit = 0
self._total_miss = 0
return
def cache(self,key,value=None):
old = self._containers.get(key,None)
# read operation
if value is None:
hit = None
if old is not None:
self._total_hit += 1
old['access'] += 1
hit = old['value']
else:
self._total_miss += 1
# early shrink
self._invoke += 1
if self._invoke > self._period:
self.shrink()
self._invoke = 0
return hit
# write operation
self.evict()
self._containers[key] = {'access' : 0, 'value':value,'generation' : self._generation}
self._current += 1
return value
def cacheable(name,size=None):
def decorate(target):
@wraps(target)
def wrap(*args,**kwds):
caches = Caches.fetch(name,size)
key = '%s-%s-%s' % (
target.__name__,
'-'.join([str(arg) for arg in args]),
'-'.join([ '%s=%s' % (key,hash(str(value))) for key,value in kwds.iteritems()])
)
hit = caches.cache(key)
if hit is not None:
return hit
hit = target(*args,**kwds)
if hit is not None:
caches.cache(key,hit)
return hit
return wrap
return decorate
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment