缓存是一种将计算结果临时存储起来的技术,以便在后续相同或类似的请求中直接使用该结果。缓存可以存储在内存、磁盘或其他介质上,以提高系统的性能和响应速度。缓存的工作原理是将计算结果与对应的输入参数关联起来,并存储在缓存中。当下次使用相同的参数进行计算时,首先检查缓存中是否存在对应的结果,如果存在,则直接返回缓存中的结果,而不必重新计算。
使用缓存可以提高系统性能和响应速度,减少计算资源的消耗。缓存适用于以下场景:
计算结果具有重复性,即相同的输入参数会产生相同的结果。
计算结果的获取代价较高,例如涉及网络请求
、数据库查询
等耗时操作。
首先来看一个例子,也就是定义了一个求斐波那契数列的函数,包含了递归的使用。
def fib(n):
if n <= 1: # 0 or 1
return n
return fib(n - 1) + fib(n - 2)
由于涉及到重复计算,这个递归函数在 n 大了以后会非常慢。,所以我们在想有没有什么办法可以降低重复运算,那么缓存就是一个不错的方法。
下边就来写一个缓存装饰器来优化它。传统方法是用个数组记录之前计算过的值,但是这种方式不够 Pythonic
from functools import wraps
def cache(func):
"""先引入一个简单的装饰器缓存,其实原理很简单,就是内部用一个字典缓存已经计算过的结果"""
store = {}
@wraps(func)
def _(n): # 这里函数没啥意义就随便用下划线命名了
if n in store:
return store[n]
else:
res = func(n)
store[n] = res
return res
return _
@cache
def f(n):
if n <= 1: # 0 or 1
return n
return f(n - 1) + f(n - 2)
functools.cache
是在Python 3.2版本中引入的,用于缓存函数的结果。而from functools import lru_cache
是在Python 3.9版本中引入的,用于实现最近最少使用(LRU
)缓存策略,允许我们将一个函数的返回值快速地缓存或取消缓存。
functools.cache
的使用方法如下:
import functools
@functools.cache
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
print(fib(10)) # 输出55
lru_cache
的使用方法如下:
from functools import lru_cache
@lru_cache(maxsize=32)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
print([fib(n) for n in range(10)])
# Output: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
二者的区别:
functools.cache
是一个简单的装饰器,用于缓存函数的结果。它不会限制缓存的大小,当缓存满时,会抛出ValueError
异常。
lru_cache
是一个装饰器,实现了最近最少使用(LRU
)缓存策略。它可以设置缓存的最大大小,当缓存满时,会自动删除最近最少使用的缓存项。如果不设置最大大小,缓存可以无限制地增长。
那个 maxsize
参数是告诉 lru_cache
,最多缓存最近多少个返回值。
注意:虽然cache函数可以提高函数的执行效率,但同时也会增加内存的消耗,因为缓存会占用一定的内存空间。因此,在应用cache函数时需要根据具体情况进行权衡和优化,避免过度使用导致内存占用过高。
另外,需要注意的是,使用cache函数缓存的结果会被保存在内存中,并且是永久性的,因此如果使用cache函数缓存了一些不再需要的结果,这些结果就会一直占用内存。为了避免这种情况,可以手动清除缓存。我们也可以轻松地对返回值清空缓存,通过这样:
fib.cache_clear()
函数缓存允许我们将一个函数对于给定参数的返回值缓存起来。当一个 I/O
密集的函数被频繁使用相同的参数调用的时候,函数缓存可以节约时间。常见的函数缓存实现方式包括字典缓存、LRU
(Least Recently Used)缓存、Trie
树等。不同的缓存方式适用于不同的情况,可以根据具体需求选择合适的缓存策略。
下面给出两个函数缓存示例:
斐波那契数列计算:
def fibonacci(n):
if n <= 1:
return n
elif n not in cache:
cache[n] = fibonacci(n - 1) + fibonacci(n - 2)
return cache[n]
cache = {}
print(fibonacci(10)) # 输出55
在这个示例中,我们定义了一个名为fibonacci
的函数来计算斐波那契数列的第n项。为了实现函数缓存,我们使用了一个名为cache
的字典来存储已经计算好的斐波那契数列的值。当调用fibonacci
函数时,首先检查输入参数是否已经在缓存中存在,如果存在则直接返回对应的值;否则,进行递归计算并将结果存入缓存中。最后,我们可以通过调用fibonacci(10)
来测试我们的函数缓存实现。
字符串匹配算法:
def string_match(text, pattern):
if pattern == "":
return 0
elif text == "":
return -1
elif pattern[0] == text[0]:
return 1 + string_match(text[1:], pattern[1:])
else:
result = string_match(text[1:], pattern)
if result != -1:
return result + 1
else:
return string_match(text, pattern[1:])
print(string_match("hello world", "world")) # 输出6
在这个示例中,我们定义了一个名为string_match
的函数来实现字符串匹配算法。为了实现函数缓存,我们可以使用一个字典来存储已经计算过的子问题的结果。当遇到相同的子问题时,可以直接从缓存中获取结果,而不需要重新计算。这样可以减少重复计算,提高算法的效率。
注意事项:
需要合理设置缓存的大小和过期策略,以避免过多的内存占用和缓存失效导致性能下降。
需要注意并发访问的问题,可以使用锁或其他同步机制来保证线程安全。
对于一些有副作用的函数或依赖于外部状态的函数,不适合使用函数缓存。
函数缓存虽然可以提高程序的性能,但也存在一些注意事项。首先,需要合理设置缓存的大小和过期策略,以避免过多的内存占用和缓存失效导致性能下降。其次,需要注意并发访问的问题,可以使用锁或其他同步机制来保证线程安全。此外,对于一些有副作用的函数或依赖于外部状态的函数,不适合使用函数缓存。
综上所述,函数缓存是一种常用的优化技术,可以显著提高程序的性能。通过合理选择缓存方式和注意相关注意事项,我们可以更好地利用函数缓存来提升代码的效率和执行速度。