2017-10-02 5 views
0

下位レベルの関数でテキストを再帰的に解析するトップレベル関数からなるトップダウンパーサを作成しています。下位レベルの関数はトップレベル関数を呼び出すことはありませんが、下位レベルの関数は相互に再帰的であることに注意してください。Pythonの再帰関数のメモ帳ですが、最上位呼び出しの間だけです。

パーザはややゆっくり実行されていることに気がつきました。パーザは同じテキストの同じタイプのオブジェクトを同じオフセットで繰り返し解析しようとする可能性があるため、再帰の指数関数的な増加が原因と思われます無駄な努力で

このような理由から、下位レベルの関数呼び出しをメモしたいのですが、トップレベル関数が返された後、memoizationキャッシュをクリアしてメモリを解放する必要があります。

つまり、ユーザーが同じパラメータで複数回トップレベル関数を呼び出すと、プログラムは実際には解析処理全体を再度実行する必要があります。

私は、同じテキストが複数回トップレベルで解析されることはないと考えています。そのため、メモリオーバーヘッドはそれほど価値がありません(それぞれの解析ではかなり大きなキャッシュが生成されます)。

一つの可能​​な解決策は、このような追加のキャッシュ引数取るために、すべての下位レベルの機能を書き換えることである:(キャッシュ引数をダウン渡す

def low_level_parse(text, start, cache): 
    if (text, start) not in cache: 
     # Do something to compute a result 
     # ... 
     cache[(text, start)] = result 

    return cache[(text, start)] 

および低レベル関数に対するすべての呼び出しを書き換えましたトップレベル関数では{}に初期設定されています)。

残念ながら、多くの低レベル解析関数があり、それぞれが他の低レベル解析関数を何度も呼び出すこともあります。このようにキャッシュを実装するコードをリファクタリングすると、非常に面倒でエラーが発生しやすくなります。

デコレータを使用する方法もありますが、これはメンテナンス性の面では最も良いと思いますが、メモ飾りデコレータの実装方法はわかりませんが、キャッシュは最上位にしか存在しません関数スコープ。

私はモジュール内のグローバル変数としてキャッシュを定義し、トップレベル関数から戻った後に明示的にクリアすることも考えました。これにより、低レベルの関数を変更して明示的にキャッシュ引数を取る必要がなくなり、グローバルキャッシュを使用するmemoizeデコレータを使用することができます。しかし、これがマルチスレッド環境で使用されている場合、グローバルキャッシュが良いアイデアであるかどうかはわかりません。

+0

1つのオプションは、クラスを作るです - メモ化のキャッシュがメンバーです。異なるスレッドは独自のインスタンスを実装できます。マルチスレッド環境に移行する可能性がある場合は、キャッシュ用にグローバルを使用することはできません(ロックを使用して目的を破ることなく)。 memoizationパラメータを使用するリファクタリングが、memizationを使用するためのrefractoringよりも難しいと言う理由はわかりませんが。 –

+0

リファクタリングの問題の理由は、キャッシュパラメータを明示的に使用するためには、各解析関数の制御フローを変更し、キャッシュ引数を渡すように各呼び出しを変更する必要があるからです。デコレータを使用すると、各関数定義の上に@memoize行を1つ追加するだけで済みます(また、他のすべての関数を修正する必要はなく、1つの場所でmemoizationコードを変更することもできます)。 –

+0

ああ、ほとんど忘れてしまった - 疑いはない。これはテストするのが簡単なので、テストしてください。 http://wiki.c2.com/?PrematureOptimization –

答えて

0

私はここに必要とされるものだと思っているDecorators with Argumentsに、このリンクを見つけた:

class LowLevelProxy: 

    def __init__(self, cache): 
     self.cache = cache 

    def __call__(self, f): 
     def wrapped_f(*args, **kwargs): 
      key = (f,args) # <== had to remove kwargs as dicts cannot be keys 
      if key not in self.cache: 
       result = f(*args, **kwargs) 
       self.cache[key] = result 

      return self.cache[key] 
     return wrapped_f 

NBラップされている各機能は、キャッシュ内の独自のセクションを持っています。

あなたは、このようなあなたの低レベルの機能のそれぞれをラップすることができるかもしれない:

@LowLevelProxy(cache) 
def low_level(param_1, param_2): 
    # do stuff 
関連する問題