Python提升程序性能:深入函数缓存详解

Python提升程序性能:深入函数缓存详解_第1张图片


概要

缓存是一种将计算结果临时存储起来的技术,以便在后续相同或类似的请求中直接使用该结果。缓存可以存储在内存、磁盘或其他介质上,以提高系统的性能和响应速度。缓存的工作原理是将计算结果与对应的输入参数关联起来,并存储在缓存中。当下次使用相同的参数进行计算时,首先检查缓存中是否存在对应的结果,如果存在,则直接返回缓存中的结果,而不必重新计算。


使用缓存可以提高系统性能和响应速度,减少计算资源的消耗。缓存适用于以下场景:

  • 计算结果具有重复性,即相同的输入参数会产生相同的结果。

  • 计算结果的获取代价较高,例如涉及网络请求数据库查询等耗时操作。

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]


二者的区别:

  1. functools.cache是一个简单的装饰器,用于缓存函数的结果。它不会限制缓存的大小,当缓存满时,会抛出ValueError异常。

  2. lru_cache是一个装饰器,实现了最近最少使用(LRU)缓存策略。它可以设置缓存的最大大小,当缓存满时,会自动删除最近最少使用的缓存项。如果不设置最大大小,缓存可以无限制地增长。

  3. 那个 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.总结

综上所述,函数缓存是一种常用的优化技术,可以显著提高程序的性能。通过合理选择缓存方式和注意相关注意事项,我们可以更好地利用函数缓存来提升代码的效率和执行速度。

你可能感兴趣的:(python,缓存,开发语言)