Tornado アプリケーション コンテキストで非同期に実行したい、重い計算ジョブを処理する関数があると想像してください。さらに、結果をディスクに保存し、同じ引数に対して関数を 2 回再実行しないことで、関数を遅延評価したいと考えています。
結果 (メモ化) をキャッシュしないと、次のようになります。
def complex_computation(arguments):
...
return result
@gen.coroutine
def complex_computation_caller(arguments):
...
result = complex_computation(arguments)
raise gen.Return(result)
関数のメモ化を実現するために、 joblibからMemoryクラスを選択します。関数を関数で装飾するだけで、簡単にメモ化できます。@mem.cache
@mem.cache
def complex_computation(arguments):
...
return result
のmem
ようなものがありますmem = Memory(cachedir=get_cache_dir())
。
ここで、エグゼキューターで計算的に複雑な関数を実行する場合、2 つを組み合わせることを検討してください。
class TaskRunner(object):
def __init__(self, loop=None, number_of_workers=1):
self.executor = futures.ThreadPoolExecutor(number_of_workers)
self.loop = loop or IOLoop.instance()
@run_on_executor
def run(self, func, *args, **kwargs):
return func(*args, **kwargs)
mem = Memory(cachedir=get_cache_dir())
_runner = TaskRunner(1)
@mem.cache
def complex_computation(arguments):
...
return result
@gen.coroutine
def complex_computation_caller(arguments):
result = yield _runner.run(complex_computation, arguments)
...
raise gen.Return(result)
最初の質問は、前述のアプローチが技術的に正しいかどうかです。
ここで、次のシナリオを考えてみましょう。
@gen.coroutine
def first_coroutine(arguments):
...
result = yield second_coroutine(arguments)
raise gen.Return(result)
@gen.coroutine
def second_coroutine(arguments):
...
result = yield third_coroutine(arguments)
raise gen.Return(result)
2 番目の質問は、どのようにメモ化できるかということですsecond_coroutine
。次のようなことをするのは正しいですか:
@gen.coroutine
def first_coroutine(arguments):
...
mem = Memory(cachedir=get_cache_dir())
mem_second_coroutine = mem(second_coroutine)
result = yield mem_second_coroutine(arguments)
raise gen.Return(result)
@gen.coroutine
def second_coroutine(arguments):
...
result = yield third_coroutine(arguments)
raise gen.Return(result)
[UPDATE I] Tornado での関数の結果のキャッシュと再利用では、2 番目の質問の解決策としてfunctools.lru_cache
orの使用について説明しています。repoze.lru.lru_cache