一文弄懂Python中的缓存机制

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 应用程序中实现缓存功能的最简单方法。

您学废了嘛?

扫码进群,交个朋友!

在这里插入图片描述

相关推荐

  1. 彻底MySQL字符集和排序规则

    2024-05-01 22:52:03       46 阅读
  2. Qt信号与槽机制

    2024-05-01 22:52:03       50 阅读
  3. Spring@Conditional注解

    2024-05-01 22:52:03       38 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-05-01 22:52:03       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-05-01 22:52:03       106 阅读
  3. 在Django里面运行非项目文件

    2024-05-01 22:52:03       87 阅读
  4. Python语言-面向对象

    2024-05-01 22:52:03       96 阅读

热门阅读

  1. 笨蛋学C++之 C++对数据库实现CRUD

    2024-05-01 22:52:03       37 阅读
  2. vue3父组件调用子组件方法

    2024-05-01 22:52:03       33 阅读
  3. 如何将API 中的excel 文件load 到 Azure blob 中

    2024-05-01 22:52:03       35 阅读
  4. .requires_grad,.detach(),torch.no_grad()

    2024-05-01 22:52:03       30 阅读
  5. C/C++中的整数除法运算与汇编指令DIV和IDIV

    2024-05-01 22:52:03       35 阅读
  6. 如何看待AIGC技术

    2024-05-01 22:52:03       29 阅读
  7. Leetcode 590:N叉树的后序遍历

    2024-05-01 22:52:03       35 阅读
  8. Ubuntu 4G模块域名ping不通

    2024-05-01 22:52:03       29 阅读