Python提升程序性能:深入函数缓存详解
概要
缓存是一种将计算结果临时存储起来的技术,以便在后续相同或类似的请求中直接使用该结果。缓存可以存储在内存、磁盘或其他介质上,以提高系统的性能和响应速度。缓存的工作原理是将计算结果与对应的输入参数关联起来,并存储在缓存中。当下次使用相同的参数进行计算时,首先检查缓存中是否存在对应的结果,如果存在,则直接返回缓存中的结果,而不必重新计算。
使用缓存可以提高系统性能和响应速度,减少计算资源的消耗。缓存适用于以下场景:
-
计算结果具有重复性,即相同的输入参数会产生相同的结果。
-
计算结果的获取代价较高,例如涉及
网络请求
、数据库查询
等耗时操作。
1. 引入
首先来看一个例子,也就是定义了一个求斐波那契数列的函数,包含了递归的使用。
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)
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()
3. 函数缓存拓展
函数缓存允许我们将一个函数对于给定参数的返回值缓存起来。当一个?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
的函数来实现字符串匹配算法。为了实现函数缓存,我们可以使用一个字典来存储已经计算过的子问题的结果。当遇到相同的子问题时,可以直接从缓存中获取结果,而不需要重新计算。这样可以减少重复计算,提高算法的效率。
注意事项:
-
需要合理设置缓存的大小和过期策略,以避免过多的内存占用和缓存失效导致性能下降。
-
需要注意并发访问的问题,可以使用锁或其他同步机制来保证线程安全。
-
对于一些有副作用的函数或依赖于外部状态的函数,不适合使用函数缓存。
函数缓存虽然可以提高程序的性能,但也存在一些注意事项。首先,需要合理设置缓存的大小和过期策略,以避免过多的内存占用和缓存失效导致性能下降。其次,需要注意并发访问的问题,可以使用锁或其他同步机制来保证线程安全。此外,对于一些有副作用的函数或依赖于外部状态的函数,不适合使用函数缓存。
4.总结
综上所述,函数缓存是一种常用的优化技术,可以显著提高程序的性能。通过合理选择缓存方式和注意相关注意事项,我们可以更好地利用函数缓存来提升代码的效率和执行速度。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!