一文弄懂Python中的缓存机制

本文介绍了如何利用Python的@cache装饰器优化递归函数如斐波那契数列的性能,以及何时适合和不适用这种优化,包括Python3.9及以上版本的使用、非确定性和副作用的考虑。
摘要由CSDN通过智能技术生成

1. 引言

谈到提高Python程序的执行性能,尤其是数据处理性能,有太多的第三方库可以帮助我们。从它们的机制来看,大多数都是依靠优化数据结构或内存利用率来实现性能提升的。

事实上,有一种Python原生缓存装饰器可以用来显著提高性能。我们不需要安装任何东西,因为它是Python 内置的。当然,它不会用于所有场景。因此,我们还将讨论什么情况下我们不应该使用它。

2. 举个栗子

让我们从一个大家都熟悉的普通例子–斐波那契数列开始。下面是使用递归的常规实现。

def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

与大多数其他编程语言一样,Python 也需要为每层递归函数建立一个 “栈”,并计算每个栈上的值。

不过,使用cache装饰器将大大提高代码性能。而且,这样做并不困难。我们只需从 functools 模块中导入,然后将装饰器添加到函数中即可。

from functools import cache
​
@cache
def fibonacci_cached(n):
    if n < 2:
        return n
    return fibonacci_cached(n-1) + fibonacci_cached(n-2)

3. 性能对比

以下是两种实现的运行结果和性能对比:
在这里插入图片描述

结果显示,启用缓存版本的性能比不启用缓存的版本大约快 120 倍!顺便说一下,上述代码中,我给 %timeit 神奇命令设置了-r 1 -n 1参数,以确保函数只执行一次。否则,缓存的 Fibonacci 函数会非常快。例如,如果我们运行函数 10000 次,除了第一次,其余 9999 次的结果都将直接从缓存中加载。因此,上述参数可确保测试只执行一次。

4. 递归场景原因

首先让我们来看看这个斐波那契递归函数的堆栈调用。为了确保能在图像中演示,我们必须简化场景。下图中显示的是 fibonacci(4) 的堆栈。
在这里插入图片描述
当我们计算函数 fibonacci(4) 时,递归函数将调用下一级的新参数,直到调用到初始值fibonacci(1)==1 fibonacci(0)==0 为止。在上图中,所有步骤都需要计算。例如,尽管在计算法f(4)时, f(0)f(1) f(2) 已经计算了多次,但它们都是分别重复计算的。

5. 缓存场景原因

接着,让我们看看使用缓存装饰器后的情况:
在这里插入图片描述

这一次,绿色的步骤不再需要重复计算。只要函数 f(x) 计算过一次,就会被缓存起来。然后,当 f(x) 再次出现时,结果将直接从内存中加载,因为它已被缓存。因此,在上图中,灰色步骤根本不需要计算。

所以,真正的堆栈如下图所示。部分f(x)函数将直接从缓存中加载。

在这里插入图片描述

从上图中我们不难理解,只有左侧边上的步骤会被实际计算,而且每个 f(x)只计算一次。这也是启用缓存后性能远高于普通递归函数的原因。此外,对于斐波那契函数这个特殊示例,我们可以得出,使用的参数数字越大,缓存带来的性能提升就越大。例如,fibonacci_cached(30) 的性能将是 fibonacci(30) 120 倍。

6. 使用场景

当然,并不是在所有地方都建议使用缓存。在我们开始在每个Python 函数上添加 @cache 之前,需要考虑以下事项。

  • Python版本
    请注意,@cache 装饰器是在Python 3.9中引入的。如果你不能使用 3.9+ 版本,请考虑使用 @lru_cache,它是一个更全面的缓存功能。我将在后续文章中介绍。

  • 不可在非确定函数中使用缓存
    当函数中存在任何非确定性内容时,我们就不应该使用缓存。例如我们使用函数datetime.now() 来获取当前的时间戳。获取当前时间戳的操作是非确定的。就是每次执行返回的值是不同的,若使用缓存,可能会导致两次不同时间调用会返回同样的时间戳,这是不能接受的。

  • 若函数有副作用,则不建议使用缓存
    这里说的 "副作用 "是指返回值之外的操作,例如向文件写入文本或更新数据库表。如果我们对这些函数使用缓存,"副作用 "就不会在第二次调用时发生。换句话说,只有当我们第一次调用函数时,它才会起作用。

7. 总结

总之,我们在本文中介绍了Python 内置functools模块中的@cache 装饰器。它可以用来提高一些典型递归函数的性能,也可以被认为是在 Python 应用程序中实现缓存功能的最简单方法。

您学废了嘛?

扫码进群,交个朋友!

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赵卓不凡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值