枣庄市网站建设_网站建设公司_色彩搭配_seo优化
2025/12/22 0:24:29 网站建设 项目流程

原文:towardsdatascience.com/how-to-boost-the-performance-of-python-using-the-caching-techniques-156368d191e8

一旦你不再是 Python 新手,那就该探索 Python 中的内置特性了。我敢打赌,Python 中会有很多出人意料的内置特性。今天,我将在这篇文章中介绍其中之一。

你是否遇到过函数必须多次执行且某些结果可以重用的场景?在下面的第一个例子中,你会看到缓存机制将递归函数的性能提高了 120 倍。因此,在这篇文章中,我将介绍 Python 3.2 版本以来就内置的functools模块中的lru_cache装饰器。

functools模块中,还有一个名为cache的装饰器,它更加直接。然而,易于使用有时意味着灵活性较低。所以,我将从cache装饰器开始,介绍它的局限性。然后,将重点关注lru_cache如何为我们提供更多灵活性。

1. @cache 装饰器的回顾

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/3fdfe21ccd603cab75706051c004f75c.png

图片由作者在 Canva 中创建

在我之前的一篇文章中,我介绍了 Python 内置的functools模块中的@cache装饰器。在那篇文章中,我使用斐波那契递归函数来展示缓存机制的力量,并且比没有缓存版本快了大约120 倍

示例如下。让我们首先编写一个斐波那契递归函数。

deffibonacci(n):ifn<2:returnnreturnfibonacci(n-1)+fibonacci(n-2)

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

然而,“cache”装饰器将显著提高性能。而且,这样做并不困难。我们只需要从functools模块中导入它,然后将装饰器添加到函数中。

fromfunctoolsimportcache@cachedeffibonacci_cached(n):ifn<2:returnnreturnfibonacci_cached(n-1)+fibonacci_cached(n-2)

这里是运行结果和性能比较。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c84b9b74513f9e5efe314cfada86113c.png

如果你想知道性能为什么有显著提升,请查看下面的文章。

如何使用 Python 内置装饰器显著提高性能

使用 lru_cache 重新编写函数

实际上,我们并不真的需要“重写”任何东西。要使用lru_cache装饰器,我们可以像使用cache装饰器一样简单地使用它。所以,只有装饰器的名称发生了变化。

fromfunctoolsimportlru_cache@lru_cachedeffibonacci_cached(n):ifn<2:returnnreturnfibonacci_cached(n-1)+fibonacci_cached(n-2)

可以看到,类似的性能提升得到了重现。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/977fb811be70fb5ac11816dc6e2f2b53.png

好的。现在,你可能想知道为什么我们需要lru_cache以及它与cache装饰器的区别是什么?接下来的部分将回答这些问题。

2. “@cache”的限制是什么?

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6300398e5c09d2150c66c139a237fc52.png

Canva 中由作者创建的图片

在我们开始介绍lru_cache装饰器之前,我们需要了解cache装饰器的限制是什么。实际上,主要的限制是内存问题。

让我模拟一个用例。假设我们正在开发一个 API 端点,从数据库获取用户详情。为了简单起见,我将跳过所有步骤,只定义一个返回随机用户的函数。所以,如果你想直接尝试,可以轻松地复制粘贴我的代码。

importrandomimportstringimporttracemallocfromfunctoolsimportcache@cachedefget_user_data(user_id):return{"user_id":user_id,"name":"User "+str(user_id),"email":f"user{user_id}@example.com","age":random.randint(18,60),"self-introduction":''.join(random.choices(string.ascii_letters,k=1000))}

在上面的代码中,使用了random模块来生成用户的年龄和自我介绍。为了模拟目的,自我介绍只是一个包含随机字符的 1000 长度字符串。

现在,让我们编写一个模拟函数来调用这个get_user_data()函数。为了理解内存使用情况,我们需要使用 Python 内置的tracemalloc模块。

defsimulate(n):tracemalloc.start()_=[get_user_data(i)foriinrange(n)]current,peak=tracemalloc.get_traced_memory()print(f"Current memory usage:{current/(1024**2):.3f}MB")tracemalloc.stop()

因此,我们运行get_user_data()函数n次。同时,我们使用tracemalloc模块来跟踪内存使用情况。

这里是10,000次模拟的内存使用情况,内存使用量大约为14MB

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c80b9a1df2861e294de63016a1dcd83d.png

这里是100,000次模拟的内存使用情况,内存使用量大约为143MB

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/878ccb066602a9c92dc2859b9758ebbd.png

这里是1,000,000次模拟的内存使用情况,内存使用量大约为1421MB

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/ac10c2a7575534ddd3d5d7b2d7ad346d.png

更重要的是,用于缓存的内存将永远不会释放,直到进程停止。在现实场景中,我们可能不知道我们需要多少内存来缓存。这可能导致无法控制的后果。

我猜你已经意识到了,这就是我们需要使用lru_cache的情况。

为什么叫“lru”?

lru_cache装饰器中的“lru”代表“使用”。这表示缓存机制使用的算法是通过保留频繁访问的数据并丢弃最少使用的数据来管理内存。

等等,这听起来我们可以为它可以缓存的最多数据添加一个上限。是的,这正是lru_cache可以给我们提供的灵活性。

3. 控制最大内存大小

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/65275cc1ea8d4b35d359e129fb7298fc.png

Canva 中由作者创建的图片

现在,让我们看看如何控制最大内存大小。

我们可以重用之前的get_user_data()函数进行比较。当然,为了演示目的,我们需要对之前的例子做一些修改。

首先,我们需要导入lru_cache,并在函数上使用这个装饰器。

importrandomimportstringimporttracemallocfromfunctoolsimportlru_cache@lru_cache(maxsize=1000)defget_user_data(user_id):return{"user_id":user_id,"name":"User "+str(user_id),"email":f"user{user_id}@example.com","age":random.randint(18,60),"self-introduction":''.join(random.choices(string.ascii_letters,k=1000))}

为了演示目的,我将设置maxsize=1000,这样我们就可以比较带有内存控制的cache装饰器的内存使用情况。

然后,我们需要修改模拟函数。这次,让我们每 100 次运行输出一次内存使用情况。

defsimulate(n):tracemalloc.start()foriinrange(n):_=get_user_data(i)ifi%100==0:current,peak=tracemalloc.get_traced_memory()print(f"Iteration{i}: Current memory usage:{current/(1024**2):.3f}MB")tracemalloc.stop()

然后,让我们模拟 2,000 次运行。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/bff0a6f926ba37ed862ef88853123060.png

显然,前 1000 次运行表明内存使用在累积。之后,对于 1001-2000 次运行,内存使用变得稳定,没有显著增加。下面的图表显示了增长趋势。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6d8a806933a01692a9d034c93bcbbd80.png

因此,当我们设置lru_cachemaxsize时,在达到允许缓存的最高对象数量之前,它将像cache装饰器一样工作。当达到上限时,它将开始从最近最少使用的缓存对象中删除。

这就是lru_cache更加灵活的原因,因为它减轻了缓存的内存担忧。

4. 缓存管理的更多工具

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/5d0d2e33438bf970c6e491e5ba73c3e4.png

Canva 作者创建的图片

好的,看到使用 Python 中的cachelru_cache如此简单真是太好了。然而,我们能否管理我们所缓存的任何内容?设置最大大小确实是一个好方法,但还有更多吗?是的,让我们看看缓存的两个控制函数。

使用 cache_info()监控性能

第一个有用的函数是cache_info()函数。它可以显示当前缓存的尺寸,缓存数据被利用的次数(命中)以及从未被使用的次数(未命中)。

让我们再次使用简单的斐波那契函数。

fromfunctoolsimportlru_cache@lru_cache(maxsize=32)deffibonacci(n):ifn<2:returnnreturnfibonacci(n-1)+fibonacci(n-2)

好的,现在让我们运行函数并检查缓存信息。

print(fibonacci(30))print(fibonacci.cache_info())

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/52ba5edc7789e1e69e9c2f710538cb43.png

我们可以看到,maxsize显示lru_cache的最大限制被设置为 32 个对象。已经有 31 个被使用(缓存)。在这 31 个缓存值中,有 28 次命中。也有 31 次未命中,这意味着有 31 次函数必须运行其逻辑来计算值,因为没有缓存的值。看起来我们的缓存策略在这个函数中非常成功。

cache_info()函数为我们提供了验证我们的缓存功能是否有所帮助的标准。例如,如果命中次数很少而未命中次数很多,这可能意味着该函数可能不值得缓存。

使用 cache_clear()重置缓存。

从其名称可以轻松猜出这个函数。是的,一旦运行它,它将清除所有缓存的成果。让我们再次使用斐波那契函数作为例子。

fromfunctoolsimportlru_cache@lru_cache(maxsize=32)deffibonacci(n):ifn<2:returnnreturnfibonacci(n-1)+fibonacci(n-2)# Run the function and check the infoprint(fibonacci(30))print(fibonacci.cache_info())# Clear the cache and check the info againfibonacci.cache_clear()print(fibonacci.cache_info())

在上述代码中,我们只是再次运行了斐波那契函数。然后,通过检查状态,它显示与之前完全相同的统计数据,当然。之后,我们运行了cache_clear()函数来截断所有缓存的成果。现在,让我们再次检查信息,将不会留下任何东西。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/72bc13b5e10a71406263c45453c48493.png

通过了解上述两个函数,我们将能够更容易地管理我们的缓存。例如,缓存的一个大问题是内存永远不会被释放,但如果我们能定期或动态地清除缓存,这个问题就可以得到缓解,甚至解决。

摘要

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/84f14212400521d1bf7fb40858a79e29.png

由作者在 Canva 中创建的图像

在这篇文章中,我首先介绍了来自 Python 内置模块functoolscache装饰器。尽管它更容易使用,但也有一些限制,例如缺乏内存控制。相反,lru_cache将为我们提供灵活性,确保在更复杂的情况下不会出现内存泄漏。

除了这些,还介绍了一些其他技巧,例如显示现有缓存的统计信息和清理缓存结果。通过有效地使用这些管理函数,我们可以实现更定制的功能,避免错误并更有效地利用内存。希望这篇文章能有所帮助!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询