为啥一种记忆策略比另一种慢?
Posted
技术标签:
【中文标题】为啥一种记忆策略比另一种慢?【英文标题】:Why is one memoization strategy slower than the other?为什么一种记忆策略比另一种慢? 【发布时间】:2014-10-18 14:11:18 【问题描述】:所以这个关于记忆的page 让我很好奇。我运行了自己的基准测试。
1) 可变默认字典:
%%timeit
def fibo(n, dic=) :
if n not in dic :
if n in (0,1) :
dic[n] = 1
else :
dic[n] = fibo(n-1)+fibo(n-2)
return dic[ n ]
fibo(30)
输出:
100000 loops, best of 3: 18.3 µs per loop
2) 相同的想法,但遵循“请求原谅比许可更容易”的原则:
In [21]:
%%timeit
def fibo(n, dic=) :
try :
return dic[n]
except :
if n in (0,1) :
dic[n] = 1
else :
dic[n] = fibo(n-1)+fibo(n-2)
return dic[ n ]
fibo(30)
输出:
10000 loops, best of 3: 46.8 µs per loop
我的问题
为什么 2) 比 1) 慢?编辑
正如@kevin 在 cmets 中建议的那样,我把装饰器弄错了,所以我把它删除了。其余的仍然有效! (希望)
【问题讨论】:
这是一个非常不寻常的装饰器。通常当你在装饰器的某个地方装饰一个函数时,你最终会在完成一些工作后调用该函数。但是你的装饰者从不打电话给f
。当您装饰 fibo
时,您实际上是在“丢弃此函数的定义,并将其替换为示例 1 中的函数”
捕获异常(这意味着堆栈跟踪)可能非常昂贵:docs.python.org/2/faq/design.html#how-fast-are-exceptions
@DmitryBychenko 我认为您应该将其发布为答案。
“请求原谅比许可更容易”的原则从何而来? :0 在编程环境中这听起来相当不合理。
@BartoszKP 这是真的。大多数时候,用户可能会给你正确的东西。这就是为什么最好假设他们给了你正确的东西,如果它错了就处理异常。这样您就可以避免hasattr
或if key in d:
的开销。该原则经常使用的另一个领域是在尝试使用键/属性之前检查它们是否存在,这(有时)更可能被实际原谅。
【参考方案1】:
捕获异常意味着堆栈跟踪,这可能非常昂贵:
https://docs.python.org/2/faq/design.html#how-fast-are-exceptions
异常在两种情况下非常有效:
try ... finally
try ... except
,前提是没有抛出异常
但是,当异常发生并捕获所需的堆栈跟踪 增加了很大的开销。
【讨论】:
【参考方案2】:方法一总共进行了三个查找(n not in dic:
、插入dic[n] =
和返回dic[n]
)。第二种方法在最坏情况中也进行了三个查找(检索尝试dic[n] =
、插入dic[n] =
和返回dic[n]
),另外还涉及异常处理。
如果一种方法与另一种方法完成相同的工作,并向其中添加一些内容,那么它显然不会更快,而且很可能会更慢。
考虑在记忆化有更多场合有用的情况下比较效率 - 即多次运行函数,以比较 摊销 复杂性。这样,第二种方法的最坏情况发生的频率会降低,并且您可以从更少的查找中获得一些东西。
版本1:
def fibo(n, dic=) :
if n not in dic :
if n in (0,1) :
dic[n] = 1
else :
dic[n] = fibo(n-1)+fibo(n-2)
return dic[ n ]
for i in range(10000):
fibo(i)
版本2:
def fibo(n, dic=) :
try :
return dic[n]
except :
if n in (0,1) :
dic[n] = 1
else :
dic[n] = fibo(n-1)+fibo(n-2)
return dic[ n ]
for i in range(10000):
fibo(i)
还有测试:
C:\Users\Bartek\Documents\Python>python -m timeit -- "import version1" 1000000 个循环,3 个中最好的:每个循环 1.64 微秒
C:\Users\Bartek\Documents\Python>python -m timeit -- "import version2" 1000000 个循环,3 个中最好的:每个循环 1.6 微秒
当函数被更频繁地使用时,缓存中会填充更多的值,从而降低异常的机会。
【讨论】:
以上是关于为啥一种记忆策略比另一种慢?的主要内容,如果未能解决你的问题,请参考以下文章
linux 进程优先级 之设置实时进程 (另一种方式是设置nice值)
linux 进程优先级 之设置实时进程 (另一种方式是设置nice值)