2016-04-17 6 views
0

Stellen Sie sich vor, Sie haben eine Funktion, die eine schwere Rechenaufgabe behandelt, die wir asynchron in einem Tornado-Anwendungskontext ausführen möchten. Außerdem möchten wir die Funktion faul auswerten, indem wir ihre Ergebnisse auf der Platte speichern und die Funktion nicht zweimal für die gleichen Argumente wiederholen.Kombinieren Tornado gen.coroutine und Joblib mem.cache Dekorateure

Ohne das Ergebnis Zwischenspeichern (memoization) würde man folgendes tun:

def complex_computation(arguments): 
    ... 
    return result 

@gen.coroutine 
def complex_computation_caller(arguments): 
    ... 
    result = complex_computation(arguments) 
    raise gen.Return(result) 

Angenommen Funktion memoization zu erreichen, wählen wir Speicher Klasse von JOBLIB. Durch einfaches Dekorieren der Funktion mit @mem.cache kann die Funktion leicht memoized werden:

@mem.cache 
def complex_computation(arguments): 
    ... 
    return result 

wo mem etwas wie mem = Memory(cachedir=get_cache_dir()) sein kann.

Betrachten wir nun die Kombination der beiden, wo wir die rechnerisch komplexe Funktion auf eine Executor auszuführen:

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) 

Die erste Frage ist, ob der vorgenannte Ansatz ist technisch korrekt?

Nun wollen wir das folgende Szenario vor:

@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) 

Die zweite Frage ist, wie man second_coroutine memoize kann? Ist es richtig, wie etwas zu tun:

@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]Caching and reusing a function result in Tornado bespricht functools.lru_cache oder repoze.lru.lru_cache als Lösung für die zweite Frage mit.

Antwort

1

Future Die Objekte, die von Tornado Koroutinen zurückgegeben sind wiederverwendbar, so dass es als functools.lru_cache allgemeinen arbeitet solchen Caches-Speicher zu verwenden, wie in this question erläutert. Stellen Sie sicher, dass Sie den Caching Decorator vor platzieren.

On-Disk-Caching (das durch das cachedir Argument zu Memory impliziert wird) ist kniffliger, da Future Objekte nicht allgemein auf Platte geschrieben werden können. Ihr TaskRunner Beispiel sollte funktionieren, aber es tut etwas grundlegend anders als die anderen, weil complex_calculation keine Coroutine ist. Ihr letztes Beispiel wird nicht funktionieren, weil es versucht, das Objekt Future in den Cache zu stellen.

Wenn Sie Objekte mit einem Dekorator zwischenspeichern möchten, benötigen Sie stattdessen einen Dekorator, der die innere Coroutine mit einer zweiten Coroutine umschließt. Etwas wie dieses:

def cached_coroutine(f): 
    @gen.coroutine 
    def wrapped(*args): 
     if args in cache: 
      return cache[args] 
     result = yield f(*args) 
     cache[args] = f 
     return result 
    return wrapped 
+0

Dank Ben. Ja. Du hast recht. Ich habe bereits 'functools.lru_cache' getestet und es funktioniert wie ein Zauber. In Bezug auf dein Beispiel nehme ich an, dass "cache" etwas ist, das durch den äußeren Kontext bereitgestellt wird, da du scheinbar nichts innerhalb des äußeren Decorator-Abschlusses definierst. Nichtsdestoweniger ist Ihr Beispiel vollkommen klar für mich, um die Idee zu begreifen. Prost! –