Created
April 12, 2017 10:14
-
-
Save zizon/f241fcc323d9051a75376e101cbb983c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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