【问题标题】: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: 的开销。该原则经常使用的另一个领域是在尝试使用键/属性之前检查它们是否存在,这(有时)更可能被实际原谅。

标签: python fibonacci memoization python-decorators


【解决方案1】:

捕获异常意味着堆栈跟踪,这可能非常昂贵

https://docs.python.org/2/faq/design.html#how-fast-are-exceptions

异常在两种情况下非常有效:

  1. try ... finally
  2. 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 微秒

    当函数被更频繁地使用时,缓存中会填充更多的值,从而降低异常的机会。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-24
      • 2015-08-19
      • 1970-01-01
      • 2018-12-04
      • 1970-01-01
      • 1970-01-01
      • 2023-04-06
      • 2014-02-04
      相关资源
      最近更新 更多