Python Lru_缓存(来自functools)是如何工作的?
尤其是使用递归代码时,Python Lru_缓存(来自functools)是如何工作的?,python,python-3.x,numpy,caching,lru,Python,Python 3.x,Numpy,Caching,Lru,尤其是使用递归代码时,lru\u缓存有了巨大的改进。我知道缓存是一种存储数据的空间,这些数据必须快速提供,并避免计算机重新计算 functools的Pythonlru缓存如何在内部工作 我在寻找一个具体的答案,它是否像Python的其他部分一样使用字典?它是否只存储返回值值 我知道Python很大程度上是建立在字典之上的,但是,我找不到这个问题的具体答案。希望有人能简化StackOverflow上所有用户的答案。 lru\u cache使用\u lru\u cache\u包装器decorator
lru\u缓存有了巨大的改进
。我知道缓存是一种存储数据的空间,这些数据必须快速提供,并避免计算机重新计算
functools的Pythonlru缓存
如何在内部工作
我在寻找一个具体的答案,它是否像Python的其他部分一样使用字典?它是否只存储返回值
值
我知道Python很大程度上是建立在字典之上的,但是,我找不到这个问题的具体答案。希望有人能简化StackOverflow上所有用户的答案。
lru\u cache
使用\u lru\u cache\u包装器
decorator(带参数的python decorator模式),它在上下文中有一个缓存
字典,在其中保存被调用函数的返回值(每个被修饰的函数都有自己的缓存dict)。字典键由参数的\u make\u key
函数生成。在下面添加了一些粗体评论:
# ACCORDING TO PASSED maxsize ARGUMENT _lru_cache_wrapper
# DEFINES AND RETURNS ONE OF wrapper DECORATORS
def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo):
# Constants shared by all lru cache instances:
sentinel = object() # unique object used to signal cache misses
cache = {} # RESULTS SAVES HERE
cache_get = cache.get # bound method to lookup a key or return None
# ... maxsize is None:
def wrapper(*args, **kwds):
# Simple caching without ordering or size limit
nonlocal hits, misses
key = make_key(args, kwds, typed) # BUILD A KEY FROM ARGUMENTS
result = cache_get(key, sentinel) # TRYING TO GET PREVIOUS CALLS RESULT
if result is not sentinel: # ALREADY CALLED WITH PASSED ARGS
hits += 1
return result # RETURN SAVED RESULT
# WITHOUT ACTUALLY CALLING FUNCTION
misses += 1
result = user_function(*args, **kwds) # FUNCTION CALL - if cache[key] empty
cache[key] = result # SAVE RESULT
return result
# ...
return wrapper
您可以查看源代码 本质上,它使用两种数据结构,一种是将函数参数映射到其结果的字典,另一种是跟踪函数调用历史的链表 缓存基本上是使用以下内容实现的,这是很自然的
cache = {}
cache_get = cache.get
....
make_key = _make_key # build a key from the function arguments
key = make_key(args, kwds, typed)
result = cache_get(key, sentinel)
更新链表的要点如下:
elif full:
oldroot = root
oldroot[KEY] = key
oldroot[RESULT] = result
# update the linked list to pop out the least recent function call information
root = oldroot[NEXT]
oldkey = root[KEY]
oldresult = root[RESULT]
root[KEY] = root[RESULT] = None
......
LRU缓存的Python 3.9源代码: Fib代码示例
@lru_cache(maxsize=2)
def fib(n):
if n == 0:
return 0
if n == 1:
return 1
return fib(n - 1) + fib(n - 2)
LRU缓存装饰器检查一些基本情况,然后用包装器_LRU_Cache_包装器包装用户函数。在包装器内部,将项添加到缓存的逻辑、LRU逻辑(即将新项添加到循环队列)以及将项从循环队列中移除的逻辑都会发生
def lru_cache(maxsize=128, typed=False):
...
if isinstance(maxsize, int):
# Negative maxsize is treated as 0
if maxsize < 0:
maxsize = 0
elif callable(maxsize) and isinstance(typed, bool):
# The user_function was passed in directly via the maxsize argument
user_function, maxsize = maxsize, 128
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
return update_wrapper(wrapper, user_function)
elif maxsize is not None:
raise TypeError(
'Expected first argument to be an integer, a callable, or None')
def decorating_function(user_function):
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
return update_wrapper(wrapper, user_function)
return decorating_function
maxsize
value的所有项目。要记住root的重要概念是在上一个(0)和下一个位置(1)中自引用自身(root[:]=[root,root,None,None])
- 第一种情况是,当
为0时,这意味着没有缓存功能,包装器包装用户函数而没有任何缓存功能。包装器递增缓存未命中计数并返回结果maxsize
def wrapper(*args, **kwds): # No caching -- just a statistics update nonlocal misses misses += 1 result = user_function(*args, **kwds) return result
def wrapper(*args, **kwds): # Simple caching without ordering or size limit nonlocal hits, misses key = make_key(args, kwds, typed) result = cache_get(key, sentinel) if result is not sentinel: hits += 1 return result misses += 1 result = user_function(*args, **kwds) cache[key] = result return result
- 第二种情况。当
为无时。在本节中,缓存中存储的元素数量没有限制。因此包装器检查缓存(字典)中的密钥。当密钥存在时,包装器返回值并更新缓存命中信息。当密钥丢失时,包装器使用用户传递的参数调用user函数,更新缓存,更新缓存未命中信息,并返回结果maxsize
def wrapper(*args, **kwds): # No caching -- just a statistics update nonlocal misses misses += 1 result = user_function(*args, **kwds) return result
def wrapper(*args, **kwds): # Simple caching without ordering or size limit nonlocal hits, misses key = make_key(args, kwds, typed) result = cache_get(key, sentinel) if result is not sentinel: hits += 1 return result misses += 1 result = user_function(*args, **kwds) cache[key] = result return result
- 第三种情况是,
是默认值(128)或用户传递的整数值。下面是实际的LRU缓存实现。以线程安全的方式将整个代码保存在包装器中。在执行任何操作之前,请从缓存中读取/写入/删除maxsize
- 缓存中的值存储为四项列表(记住根)。第一项是对前一项的引用,第二项是对下一项的引用,第三项是特定函数调用的键,第四项是结果。下面是斐波那契函数参数1的实际值
。[…]指对自我(列表)的引用[[…]、[…]、1、1]、[…]、[…]、1、1]、无、无]
- 第一个检查是缓存命中。如果是,缓存中的值是四个值的列表
当项目已在缓存中时,无需检查循环队列是否已满或从缓存中弹出项目。而是更改循环队列中项目的位置。由于最近使用的项目始终位于顶部,因此代码将移动到队列顶部的最近值,并且上一个顶部项目将成为当前项目的下一个nonlocal root, hits, misses, full key = make_key(args, kwds, typed) with lock: link = cache_get(key) if link is not None: # Move the link to the front of the circular queue print(f'Cache hit for {key}, {root}') link_prev, link_next, _key, result = link link_prev[NEXT] = link_next link_next[PREV] = link_prev last = root[PREV] last[NEXT] = root[PREV] = link link[PREV] = last link[NEXT] = root hits += 1 return result
和last[next]=root[PREV]=link
和link[PREV]=last
。“下一步”和“上一步”在顶部初始化,指向链接字段的“上一步、下一步、键、结果=0、1、2、3”名称列表中的适当位置。最后,增加缓存命中信息并返回结果link[next]=root
def wrapper(*args, **kwds): # No caching -- just a statistics update nonlocal misses misses += 1 result = user_function(*args, **kwds) return result
def wrapper(*args, **kwds): # Simple caching without ordering or size limit nonlocal hits, misses key = make_key(args, kwds, typed) result = cache_get(key, sentinel) if result is not sentinel: hits += 1 return result misses += 1 result = user_function(*args, **kwds) cache[key] = result return result
- 当为缓存未命中时,更新未命中信息,代码检查三种情况。这三个操作都是在获得RLock后进行的。源代码中的三种情况按以下顺序排列-在缓存中找到锁密钥后,缓存已满,并且缓存可以接收新项。为了演示,让我们按照以下顺序操作:当缓存未满时,缓存已满,并且在获取锁后,密钥在缓存中可用
- 当缓存未满时,准备最近的
以包含根以前的引用、根、键和计算结果结果(link=[last,root,key,result])
- 然后将最近的结果(link)指向循环队列的顶部(
),root的上一个项目的next to指向最近的结果(root[PREV]=link
),并将最近的结果添加到缓存中(last[next]=link
)cache[key]=link
- 最后,检查缓存是否已满(
cache\u len()>=maxsize和cache\u len=cache.\uu len\uuuuuuuuuuuuuuuuuuuu)并将状态设置为“已满”
- 对于fib示例,当函数接收到第一个值
时,root为空,root值为1
且在[…]、[…]、None、None]