为啥一种记忆策略比另一种慢?

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 这是真的。大多数时候,用户可能会给你正确的东西。这就是为什么最好假设他们给了你正确的东西,如果它错了就处理异常。这样您就可以避免hasattrif 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值)

软WAF的另一种思路:基于Openresty+Naxsi的WAF实现

Hibernate检索策略

多个互斥锁策略以及为啥库不使用地址比较

Ribbon-负载均衡策略