Python functools.lru_cache 实现高速缓存及其原理
原理
lru_cache
的实现,依赖于Python
的闭包,以及LRU
算法。另外,这个缓存方式是线程安全的,其生命周期,开始于进程创立后的被装饰函数的的第一次运行,直到进程结束
- 借助
Python
的闭包,实现函数结果的高速缓存 - 借助
LRU
算法(最近最少使用),实现函数结果的高速缓存更新。 - 如果,
maxsize=None
,则将禁用LRU
功能,并且缓存可以无限增长。 - 如果将
typed
设置为true
,则将分别缓存不同类型的函数参数。例如,f(3)和f(3.0)将被视为具有不同结果的不同调用 - 由于使用字典来缓存结果,因此函数的位置和关键字参数必须是可哈希的。
- 可以将不同的参数模式视为具有单独的缓存项的不同调用。例如,
f(a=1,b=2)
和f(b=2,a=1)
的关键字参数顺序不同,并且可能具有两个单独的缓存条目。 - 借助
func.cache_info()
方法,查看缓存信息 - 借助
func.cache_clear()
方法,清除缓存信息
讲解中,注释写的比较明白,如想进一步了解,可以仔细阅读
原理测试
功能
下面这个测试函数,只会在第一次运行的时候,停顿1s。其它次运行,不会停顿
import time
from functools import lru_cache
@lru_cache
def sleep(x, y):
time.sleep(1)
print('I woke up')
return x * 10, y * 10
sleep(1, 2)
sleep(1, 2)
缓存原理测试
可以仔细观察,闭包参数的变化,执行函数前后,变化的参数前五个参数(下表中的最上面的5个参数,参数含义参考下文函数讲解)。由此可见,这个装饰器的缓存位置确实是利用闭包来实现
closure_pre = sleep.__closure__
for i in closure_pre:
print(f'| {i} | {i.cell_contents} |')
sleep(1, 2)
closure = sleep.__closure__
for i in closure:
print(f' {i} | {i.cell_contents} |')
print('| 第一次运行前 | 闭包参数 | 第一次运行后 | 闭包参数 |')
print('|--|--|--|--|')
参数名 | 第一次运行前 | 闭包参数 | 第一次运行后 | 闭包参数 |
---|---|---|---|---|
cache | <cell at 0x000001570D814B80: dict object at 0x000001570D554440> | {} | <cell at 0x000001570D814B80: dict object at 0x000001570D554440> | {[1, 2]: [[[…], […], None, None], [[…], […], None, None], [1, 2], (10, 20)]} |
full | <cell at 0x000001570D814C10: bool object at 0x00007FF8232CD770> | False | <cell at 0x000001570D814C10: bool object at 0x00007FF8232CD770> | False |
misses | <cell at 0x000001570D814C40: int object at 0x00007FF823311680> | 0 | <cell at 0x000001570D814C40: int object at 0x00007FF823311680> | s0 |
hits | <cell at 0x000001570D814D00: int object at 0x00007FF823311680> | 0 | <cell at 0x000001570D814D00: int object at 0x00007FF8233116A0> | 1 |
root | <cell at 0x000001570D814D30: list object at 0x000001570D7F4C80> | [[…], […], None, None] | <cell at 0x000001570D814D30: list object at 0x000001570D7F4C80> | [[[…], […], [1, 2], (10, 20)], [[…], […], [1, 2], (10, 20)], None, None] |
原函数 | <cell at 0x000001570D814DF0: function object at 0x000001570D5A8940> | <function sleep at 0x000001570D5A8940> | <cell at 0x000001570D814DF0: function object at 0x000001570D5A8940> | <function sleep at 0x000001570D5A8940> |
PREV | <cell at 0x000001570D644250: int object at 0x00007FF823311680> | 0 | <cell at 0x000001570D644250: int object at 0x00007FF823311680> | 0 |
NEXT | <cell at 0x000001570D6441F0: int object at 0x00007FF8233116A0> | 1 | <cell at 0x000001570D6441F0: int object at 0x00007FF8233116A0> | 1 |
KEY | <cell at 0x000001570D5396A0: int object at 0x00007FF8233116C0> | 2 | <cell at 0x000001570D5396A0: int object at 0x00007FF8233116C0> | 2 |
RESULT | <cell at 0x000001570D8143D0: int object at 0x00007FF8233116E0> | 3 | <cell at 0x000001570D8143D0: int object at 0x00007FF8233116E0> | 3 |
maxsize | <cell at 0x000001570D814CD0: int object at 0x00007FF823312680> | 128 | <cell at 0x000001570D814CD0: int object at 0x00007FF823312680> | 128 |
typed | <cell at 0x000001570D814D90: bool object at 0x00007FF8232CD770> | False | <cell at 0x000001570D814D90: bool object at 0x00007FF8232CD770> | False |
cache_len | <cell at 0x000001570D814BE0: method-wrapper object at 0x000001570D814E20> | <method-wrapper ‘len’ of dict object at 0x000001570D554440> | <cell at 0x000001570D814BE0: method-wrapper object at 0x000001570D814E20> | <method-wrapper ‘len’ of dict object at 0x000001570D554440> |
cache_get | <cell at 0x000001570D814BB0: builtin_function_or_method object at 0x000001570D7D3BD0> | <built-in method get of dict object at 0x000001570D554440> | <cell at 0x000001570D814BB0: builtin_function_or_method object at 0x000001570D7D3BD0> | <built-in method get of dict object at 0x000001570D554440> |
make_key | <cell at 0x000001570D814CA0: function object at 0x000001570D81C310> | <function _make_key at 0x000001570D81C310> | <cell at 0x000001570D814CA0: function object at 0x000001570D81C310> | <function _make_key at 0x000001570D81C310> |
lock | <cell at 0x000001570D814C70: _thread.RLock object at 0x000001570D814E40> | <unlocked _thread.RLock object owner=0 count=0 at 0x000001570D814E40> | <cell at 0x000001570D814C70: _thread.RLock object at 0x000001570D814E40> | <unlocked _thread.RLock object owner=0 count=0 at 0x000001570D814E40> |
LRU算法
这里主要是借助循环双链表实现的,参考下图,理解LRU实现过程。
初始化
添加第一个缓存
添加第二个缓存
重新运行第一次调用
缓存已经到了最大值,最旧的缓存替换为新缓存
图解绘图来源
Python Tutor,交互式网站,可以查看变量的引用动画
绘图代码,一次运行下面代码,观察变量变化
# 初始化