2011-02-08 7 views
0

ここからメモ化デコレータのために(doctestを持つ)受け入れ答えを使用する:What can be done to speed up this memoization decorator?これらのメソッドコールのタイミングに大きな違いが生じる原因は何ですか?

、次のコード(fib.py):

class O(object): 

    def nfib(self,n): # non-memoized fib fn 

    if n in (0, 1): 
     return n 
    return self.nfib(n-1) + self.nfib(n-2) 

    @Memoized 
    def fib(self,n): # memoized fib fn 

    if n in (0, 1): 
     return n 
    return self.fib(n-1) + self.fib(n-2) 


if __name__ == '__main__': 
    import time 
    o = O() 

    stime = time.time() 
    print "starting non-memoized" 
    for i in range(10): 
    print o.nfib(32) 
    print "finished non-memoized - elapsed secs =", time.time() - stime 

    stime = time.time() 
    print "starting memoized" 
    for i in range(10): 
    print o.fib(32) 
    print "finished memoized - elapsed secs =", time.time() - stime 

    stime = time.time() 
    print "starting memoized with reset" 
    for i in range(10): 
    Memoized.reset() 
    print o.fib(32) 
    print "finished memoized with reset - elapsed secs =", time.time() - stime 

私は次のような出力が得られます。

C:\TEMP>python fib.py 
starting non-memoized 
2178309 
2178309 
2178309 
2178309 
2178309 
2178309 
2178309 
2178309 
2178309 
2178309 
finished non-memoized - elapsed secs = 16.4189999104 
starting memoized 
2178309 
2178309 
2178309 
2178309 
2178309 
2178309 
2178309 
2178309 
2178309 
2178309 
finished memoized - elapsed secs = 0.00100016593933 
starting memoized with reset 
2178309 
2178309 
2178309 
2178309 
2178309 
2178309 
2178309 
2178309 
2178309 
2178309 
finished memoized with reset - elapsed secs = 0.00299978256226 

C:\TEMP> 

私は3番目のループが最初のループの長さになると予想していました。なぜなら、ループを通過するたびにキャッシュがリセットされるからです。 fibメソッドにデバッグステートメントを挿入すると、キャッシュされていないことが示され、実際には3番目のループで結果が計算されますが、最初のループよりもはるかに高速です。なぜ???

私は恥ずかしがっていることを見落としてしまったと思っていますが、私の好奇心は現在私の誇りを上回っています。 (私は、Windows 7のプロマシンで64ビットのPython 2.7を使用しています)。

ありがとう。

+1

私はこのコメントを前に説明しました。キャッシュはO(n^2)関数をO(n)にします。 –

+0

@グレン:...はい、あなたは(あなたは不明ではありませんでした)...私はちょうど "それを"得ていませんでした。 ...デバッグステートメントを挿入してfib(覚えていない)の呼び出しを印刷し、2ギガバイトのファイルで終了し、毎回すべての呼び出し引数を表示する - 私は今すぐ "取得"!デコレータにもう一度感謝します。 – Gerrat

答えて

6

素朴なフィボナッチ関数の呼び出しツリーは線形または三角形ではなく、多次元ピラミッドです。あなたが一度でもメモを書くことによって、の広大なの量の呼び出しをツリーから取り除き、ピラミッドをほぼ線形の構造に変えます。

+0

ありがとう...どういうわけか、この再帰関数がどのくらいの労力を費やしていたかを十分には理解していませんでした。 – Gerrat

3

最後の各結果を計算した後で3番目のループがリセットされますが、再帰呼び出しのメモ処理には依然としてメリットがあります。

1

time.time()はタイムコード、特にウィンドウに使用しないでください! timeit moduleを使用してください。ここに古いintoductionがあります。 1つの大きな問題は、コンピュータが常に何百ものことをしていることです。 Outlookが電子メールをフェッチしている間に1つの部分が実行されると、Outlookが終了した後に他の機能よりもわずかに時間がかかることがあります。タスクを繰り返し、最小限に抑える方が良い傾向があります。これと他の多くのものは、timeitモジュールによって自動的に処理されます。窓について

が、この部分はかなり関連している:Windowsでは

場合、time.clock()はマイクロ秒の精度を持っていますが、 time.time()の粒度は、第二

の1 /第60回です
+2

1/60秒はこのテストで十分正確です。 – Falmarri

+0

真実ですが、私は実際に「正確な」結果に近づくものは探していませんでした。私たちはスピードの差を見ています...そして、最初の時間は16秒を超えています...(明らかに1/60秒の時間を掛けてください): – Gerrat

+0

@Falmarri:ケース2と3を比較するとない。 – eat