Python中的高效记忆
Posted
技术标签:
【中文标题】Python中的高效记忆【英文标题】:Efficient memoization in Python 【发布时间】:2012-02-24 20:47:02 【问题描述】:我有一些任务要解决,目前最重要的部分是使脚本尽可能节省时间。我正在尝试优化的元素之一是在其中一个函数中进行记忆。
所以我的问题是:以下 3-4 种方法中哪一种是在 Python 中实现记忆的最有效/最快的方法?
我提供的代码仅作为示例 - 如果其中一种方法更有效,但不是我提到的情况,请分享你所知道的。
解决方案 1 - 使用外部范围的可变变量
此解决方案通常显示为示例记忆,但我不确定它的效率如何。我听说使用全局变量(在这种情况下,它是外部变量,而不是全局范围的变量)效率较低。
def main():
memo =
def power_div(n):
try:
return memo[n]
except (KeyError):
memo[n] = (n ** 2) % 4 # example expression, should not matter
return memo[n]
# extensive usage of power_div() here
解决方案 2 - 使用默认的可变参数
我在某处发现过去使用默认可变参数从外部范围传递变量,当 Python 首先在本地范围内搜索变量时,然后在全局范围内,跳过非本地范围(在这种情况下函数main()
) 内的范围。因为默认参数仅在定义函数时才被初始化,并且只能在内部函数内部访问,也许它因此更有效?
def main():
def power_div(n, memo=):
try:
return memo[n]
except (KeyError):
memo[n] = (n ** 2) % 4 # example expression, should not matter
return memo[n]
# extensive usage of power_div() here
或者也许以下版本(实际上是解决方案 1 和 2 的组合)更有效?
def main():
memo =
def power_div(n, memo=memo):
try:
return memo[n]
except (KeyError):
memo[n] = (n ** 2) % 4 # example expression, should not matter
return memo[n]
# extensive usage of power_div() here
解决方案 3 - 函数的属性
这是 Python 中另一个非常常见的 memoization 示例 - memoization 对象存储为函数本身的属性。
def main():
def power_div(n):
memo = power_div.memo
try:
return memo[n]
except (KeyError):
memo[n] = (n ** 2) % 4 # example expression, should not matter
return memo[n]
# extensive usage of power_div() here
总结
我对您对上述四种记忆化解决方案的意见非常感兴趣。同样重要的是,使用 memoization 的函数在另一个函数中。
我知道还有其他用于记忆的解决方案(例如Memoize
decorator),但我很难相信这是比上面列出的更有效的解决方案。如果我错了,请纠正我。
提前致谢。
【问题讨论】:
与大多数“哪个更快”的问题一样,最终的答案是“尝试并找出答案”。timeit
模块提供了一种非常好的方法来测试这样的事情。
(另外:你有没有分析过你现有的代码并发现 memoization 是一个瓶颈?如果没有,你为什么要专注于优化它?)
@Amber:案例是1)我现有的代码没有太多需要优化的地方,所以我正在尽我所能改进,2)这个问题更多的是关于上述案例的效率为什么一个比另一个更好,它更普遍。我不想使用timeit
,因为 1) 我可能缺少其他一些更有效的解决方案。 2)由于我使用记忆的方式,我的结果可能有偏差。我正在尝试找到最快的方法来使用 memoization 来学习它并让人们知道,不一定要修复这段代码(这样的问题会过于本地化)。
我的直接假设是使用dict
对象的get()
方法比捕获KeyError
更快。但可能加速只会影响“缓存未命中”分支,在这种情况下不值得。但这可能值得双向计时。
@DanielPryden:我一直在考虑使用get()
,但是如果没有找到密钥,您需要计算一些东西,它看起来像:memo.get(n, (n ** 2) % 4)
。在这种情况下,它没有多大意义,因为每次调用函数时都会执行(n ** 2) % 4
(因此记忆化是无用的)。
【参考方案1】:
不同风格的变量访问已经在http://code.activestate.com/recipes/577834-compare-speeds-of-different-kinds-of-access-to-var进行了计时和比较 这是一个快速总结:本地访问胜过非本地(嵌套范围),后者胜过全局访问(模块范围),后者胜过对内置函数的访问。
您的解决方案 #2(具有本地访问权限)应该会胜出。解决方案#3 有一个慢点查找(需要字典查找)。解决方案 #1 使用非本地(嵌套范围)访问,该访问使用单元变量(比 dict 查找快,但比本地慢)。
另外请注意,KeyError 异常类是一个全局查找,可以通过本地化来加速。您可以完全替换 try/except 并使用 memo.get(n, sentinel)
代替。甚至可以通过使用绑定方法来加快速度。当然,您最简单的速度提升可能只是来自尝试pypy :-)
简而言之,有很多方法可以调整此代码。只要确保它是值得的。
【讨论】:
非常感谢:) 您认为使用memo=memo
(其中memo
在非本地范围内)和memo=
(因此没有非本地范围)之间的性能有差异吗参与)?
@Tadeck 应该没有区别。两种方式最终都会有一个直接指向 dict 实例的局部变量。【参考方案2】:
为了那些在寻找在 python 中进行记忆的方法时偶然发现这个问题的人的利益,我推荐fastcache。
它适用于 python 2 和 3,比上述任何方法都快,并且提供了限制缓存大小的选项,以免无意中变得太大:
from fastcache import clru_cache
@clru_cache(maxsize=128, typed=False)
def foo(cat_1, cat_2, cat_3):
return cat_1 + cat_2 + cat_3
安装fastcache很简单,使用pip
:
pip install fastcache
或conda
:
conda install fastcache
【讨论】:
在 python 3 上,您可以使用本机 functools.lru_cache。根据我的实验,它的运行速度甚至比 fastcache 版本还要快。以上是关于Python中的高效记忆的主要内容,如果未能解决你的问题,请参考以下文章