""" Module containing (e.g. LRU) cache functionality for binary_c-python. We use cachetools when possible because this allows us to set up the cache of the appropriate size for the task in the grid_options dict. Please see the LRU_* options in there. """ import cachetools import curses import functools import importlib import inspect import time class cache(): class NullCache(cachetools.Cache): """ A cachetools cache object that does as little as possible and never matches. """ def __init__(self, *args, **kwargs): return None def popitem(self): return # do nothing def __getitem__(self, key): return self.__missing__(key) def __setitem__(self, key, value): return def __delitem__(self, key): return def __init__(self, **kwargs): # don't do anything: we just inherit from this class return def setup_function_cache(self,vb=False,type=None): """ Function to wrap binary_c-python's functions in function cache. The functions listed in self.grid_options['function_cache_functions'] are given caches of size self.grid_options['function_cache_size'][func] Args: None """ # choose a type of cache # add our custom NullCache to the cachetools selection setattr(cachetools,'NullCache',self.NullCache) if not self.grid_options["function_cache"]: # no function cache: set all to NullCache for func in self.grid_options['function_cache_functions'].keys(): self.function_cache[func] = cachetype(NullCache) for func in self.grid_options['function_cache_functions'].keys(): (maxsize,cachetype,testargs) = self.grid_options['function_cache_functions'].get(func) # which cache should we use? if type: # use type passed in, if given usecachetype = type elif not self.grid_options["function_cache"]: # function cache is disabled, use NoCache usecachetype = "NoCache" else: if cachetype == None: # use the default type usecachetype = self.grid_options["function_cache_default_type"] else: # use type passed in usecachetype = cachetype if vb: print("Setup cache for func {func} : maxsize={maxsize}, cachetype={cachetype} -> use {usecachetype}".format( func=func, maxsize=maxsize, cachetype=cachetype, usecachetype=usecachetype, )) if usecachetype == 'TTLCache': extra_cacheargs = [self.grid_options["function_cache_TTL"]] else: extra_cacheargs = [] # detect if the function is already wrapped x = func.split('.') modulename = 'binarycpython.utils.' + x[0] module = importlib.import_module(modulename) _method = eval('module.{}.{}'.format(x[0],x[1])) _wrapped = getattr(_method,"__wrapped__",False) # if function is wrapped... if _wrapped and id(_method) != id(_wrapped): # save the wrapped function (this calls the cache) if not (func in self.cached_function_cache): self.cached_function_cache[func] = _method self.original_function_cache[func] = _wrapped if usecachetype == 'NoCache': # unwrap if we're after NoCache _code = 'module.{}.{} = _wrapped'.format(x[0],x[1]) exec(_code) else: # function isn't wrapped, which means it was previously # unwrapped, so rewrap it if not using NoCache if usecachetype != 'NoCache': _code = 'module.{}.{} = self.cached_function_cache["{}"]'.format(x[0],x[1],func) exec(_code) # check we're not still wrapped _method = eval('module' + '.' + x[0] + '.' + x[1]) _wrapped = getattr(_method,"__wrapped__",False) # if NoCache (explicity use no cache), just use NullCache # (it's never actually set) if usecachetype == "NoCache": cachetools_func = getattr(cachetools,"NullCache") else: cachetools_func = getattr(cachetools,usecachetype) if maxsize == 0: maxsize = self.grid_options['function_cache_default_maxsize'] if vb: print("Make function cache for func {func}, maxsize {maxsize}".format( func=func, maxsize=maxsize )) # set up cache function args if maxsize is None: args = [2] else: args = [maxsize] args += extra_cacheargs # clear any existing cache if func in self.caches: try: self.caches[func].cache_clear() except: pass del self.caches[func] # set up new cache using the appropriate cachetools function if usecachetype != "NoCache": self.caches[func] = cachetools_func(*args) def test_caches(self,dt=5.0): """ Function to test cache speeds of the functions that binary_c-python automatically caches. Args: dt (default 5) in seconds the length of each test. Long is more accurate, but takes longer (obviously). """ cachetypes = ("NullCache","FIFOCache","LRUCache","TTLCache","NoCache") functions = self.grid_options['function_cache_functions'].keys() maxsizes = (0,1,2,4,8,16,32,64,128,256) stdscr = curses.initscr() #curses.noecho() stdscr.clear() width = len(maxsizes) * 9 height = (len(cachetypes) + 2) * len(functions) def _coord(n,x,y,xoffset,yoffset): # map n,x,y,xoffset,yoffset to screen coords _x = (x + xoffset) * 9 _y = (len(cachetypes) + 4) * n + (y + yoffset) return (_x,_y) def _print(n,x,y,xoffset,yoffset,str): # print str at n,x,y,offset _c = _coord(n,x,y,xoffset,yoffset) stdscr.addstr(_c[1],_c[0],str) stdscr.refresh() self.grid_options['function_cache'] = True for n,func in enumerate(functions): _print(n,0,0,0,0, "Cache speed test of function {func}".format(func=func)) for x,maxsize in enumerate(maxsizes): _print(n,x,0,2,1,str(maxsize)) best = 0 best_type = None best_maxsize = None for y,type in enumerate(cachetypes): _print(n,0,y,0,2,type) self.grid_options['function_cache_default_type'] = type self.setup_function_cache() (maxsize,cachetype,testargs) = self.grid_options['function_cache_functions'].get(func) x = func.split('.') modulename = 'binarycpython.utils.' + x[0] module = importlib.import_module(modulename) _method = eval('module.{}.{}'.format(x[0],x[1])) if testargs: for x,maxsize in enumerate(maxsizes): if type == 'NoCache' and maxsize > 0: continue t0 = time.time() count = 0 while time.time() - t0 < dt: _method(self,*testargs) count += 1 if count < 99999: _print(n,x,y,2,2,"{:d}".format(count)) else: _print(n,x,y,2,2,"{:.2e}".format(float(count))) if count > best: best = count best_type = type best_maxsize = maxsize _print(n,0,len(cachetypes)+2,0,0,"Best cache type {type} with maxsize {maxsize}".format(type=best_type,maxsize=best_maxsize)) stdscr.refresh() stdscr.getkey() stdscr.keypad(0) curses.echo() print("\npress any key to exit\n") curses.endwin()