Frage

Ich habe eine teure Funktion, die eine kleine Datenmenge (ein paar Ganzzahlen und Schwimmer) übernimmt und zurückgibt. ich habe schon memoisiert Diese Funktion, aber ich möchte das Memo anhaltend machen. Es gibt bereits ein paar Themen, die sich darauf beziehen, aber ich bin mir nicht sicher, ob potenzielle Probleme mit einigen der vorgeschlagenen Ansätze vorgeschlagen werden, und ich habe einige ziemlich spezifische Anforderungen:

  • Ich werde die Funktion definitiv gleichzeitig aus mehreren Threads und Prozessen verwenden (beide verwenden multiprocessing und aus getrennten Python -Skripten)
  • Ich muss nicht von außerhalb dieser Python -Funktion gelesen oder schreiben Zugriff auf das Memo schreiben
  • Ich bin nicht so besorgt darüber, dass das Memo in seltenen Fällen beschädigt wird (wie das Ziehen des Steckers oder das versehentliches Schreiben in die Datei, ohne sie zu sperren), so wie es nicht ist das teuer wieder aufzubauen (in der Regel 10-20 Minuten), aber ich würde es vorziehen, wenn es aufgrund von Ausnahmen nicht beschädigt wäre oder einen Python-Prozess manuell beendet (ich weiß nicht, wie realistisch das ist)
  • Ich würde Lösungen, die keine großen externen Bibliotheken erfordern, dringend bevorzugen
  • Ich habe eine schwache Präferenz für plattformübergreifende Code, aber ich werde dies wahrscheinlich nur unter Linux verwenden

Dieser Thread diskutiert die shelve Modul, das anscheinend nicht prozesssicher ist. Zwei der Antworten schlagen vor fcntl.flock Um die Regaldatei zu sperren. Einige der Antworten in Dieser Thread, Es scheinen jedoch darauf hinzudeuten, dass dies mit Problemen behaftet ist - aber ich bin mir nicht ganz sicher, was sie sind. Es klingt so, als ob dies auf Unix beschränkt wäre (obwohl Windows ein Äquivalent genannt hat msvcrt.locking), und das Schloss ist nur "Beratung" - dh es wird mich nicht davon abhalten, versehentlich in die Datei zu schreiben, ohne zu überprüfen, ob sie gesperrt ist. Gibt es andere potenzielle Probleme? Würde das Schreiben in eine Kopie der Datei und das Ersetzen der Master -Kopie als letzten Schritt das Korruptionsrisiko verringern?

Es sieht nicht so aus, als ob die DBM -Modul wird besser als Regale sein. Ich habe mich kurz angesehen Sqlite3, aber es scheint ein bisschen übertrieben zu diesem Zweck. Dieser Thread und Dieses hier Erwähnen Sie mehrere Bibliotheken der Drittanbieter, einschließlich Zodb, Aber es gibt viele Möglichkeiten, und alle scheinen für diese Aufgabe übermäßig groß und kompliziert zu sein.

Hat jemand einen Rat?

AKTUALISIEREN: Kindall erwähnte Incpy unten, was sehr interessant aussieht. Leider möchte ich nicht zu Python 2.6 zurückkehren (ich benutze tatsächlich 3.2), und es sieht so aus, als wäre es ein bisschen unangenehm, mit C -Bibliotheken zu verwenden (ich nutze unter anderem Numpy und Scipy stark).

Die andere Idee von Kindall ist aufschlussreich, aber ich denke, die Anpassung an mehrere Prozesse wäre etwas schwierig - ich nehme an, es wäre am einfachsten, die Warteschlange durch Dateisperrung oder Datenbank zu ersetzen.

Wenn Sie sich ZODB wieder ansehen, sieht es perfekt für die Aufgabe aus, aber ich möchte wirklich vermeiden, zusätzliche Bibliotheken zu verwenden. Ich bin mir immer noch nicht ganz sicher, was alle Probleme mit einfach verwenden flock Sind ich mir vor, ein großes Problem ist, wenn ein Prozess beim Schreiben in die Datei oder vor der Veröffentlichung des Schlosses beendet wird?

Also habe ich den Rat von SynthesizerPatel aufgenommen und mit SQLite3 gegangen. Wenn jemand interessiert ist, habe ich mich entschlossen, einen Drop-In-Ersatz vorzunehmen dict Das speichert seine Einträge als Gurken in einer Datenbank (ich mache mir nicht die Mühe, einen Speicher zu behalten, da der Datenbankzugriff und die Wahlung im Vergleich zu allem, was ich tue, schnell genug ist). Ich bin sicher, es gibt effizientere Möglichkeiten, dies zu tun (und ich habe keine Ahnung, ob ich immer noch Probleme mit Parallelität habe), aber hier ist der Code:

from collections import MutableMapping
import sqlite3
import pickle


class PersistentDict(MutableMapping):
    def __init__(self, dbpath, iterable=None, **kwargs):
        self.dbpath = dbpath
        with self.get_connection() as connection:
            cursor = connection.cursor()
            cursor.execute(
                'create table if not exists memo '
                '(key blob primary key not null, value blob not null)'
            )
        if iterable is not None:
            self.update(iterable)
        self.update(kwargs)

    def encode(self, obj):
        return pickle.dumps(obj)

    def decode(self, blob):
        return pickle.loads(blob)

    def get_connection(self):
        return sqlite3.connect(self.dbpath)

    def  __getitem__(self, key):
        key = self.encode(key)
        with self.get_connection() as connection:
            cursor = connection.cursor()
            cursor.execute(
                'select value from memo where key=?',
                (key,)
            )
            value = cursor.fetchone()
        if value is None:
            raise KeyError(key)
        return self.decode(value[0])

    def __setitem__(self, key, value):
        key = self.encode(key)
        value = self.encode(value)
        with self.get_connection() as connection:
            cursor = connection.cursor()
            cursor.execute(
                'insert or replace into memo values (?, ?)',
                (key, value)
            )

    def __delitem__(self, key):
        key = self.encode(key)
        with self.get_connection() as connection:
            cursor = connection.cursor()
            cursor.execute(
                'select count(*) from memo where key=?',
                (key,)
            )
            if cursor.fetchone()[0] == 0:
                raise KeyError(key)
            cursor.execute(
                'delete from memo where key=?',
                (key,)
            )

    def __iter__(self):
        with self.get_connection() as connection:
            cursor = connection.cursor()
            cursor.execute(
                'select key from memo'
            )
            records = cursor.fetchall()
        for r in records:
            yield self.decode(r[0])

    def __len__(self):
        with self.get_connection() as connection:
            cursor = connection.cursor()
            cursor.execute(
                'select count(*) from memo'
            )
            return cursor.fetchone()[0]
War es hilfreich?

Lösung

SQLite3 Out of the Box bietet SÄURE. Die Dateisperrung ist anfällig für Rassenkonditionen und Parallelitätsprobleme, die Sie nicht mit SQLite3 haben.

Grundsätzlich ist SQLite3 mehr als das, was Sie brauchen, aber es ist keine große Belastung. Es kann auf Mobiltelefonen ausgeführt werden, daher ist es nicht so, als würden Sie sich für eine tierische Software verpflichten. Es wird Ihnen Zeit sparen, Räder neu zu erfinden und Sperrprobleme zu debuggen.

Andere Tipps

Ich gehe davon aus, dass Sie die Ergebnisse der Funktion im RAM, wahrscheinlich in einem Wörterbuch, weiterhin merken möchten, aber die Persistenz verwenden, um die "Warmup" -Ponent der Anwendung zu verkürzen. In diesem Fall werden Sie nicht zufällig auf Artikel zugreifen direkt im Hintergeschäft Eine Datenbank könnte also tatsächlich übertrieben sein (obwohl als als SynthesizerPatel Anmerkungen, vielleicht nicht so viel wie Sie denken).

Wenn Sie jedoch Ihre eigenen rollen möchten, könnte eine praktikable Strategie sein, das Wörterbuch einfach zu Beginn Ihres Laufs vor dem Starten von Threads aus einer Datei zu laden. Wenn ein Ergebnis nicht im Wörterbuch nicht im Wörterbuch ist, müssen Sie es in die Datei schreiben, nachdem Sie es dem Wörterbuch hinzugefügt haben. Sie können dies tun, indem Sie es einer Warteschlange hinzufügen und einen einzelnen Worker -Thread verwenden, der Elemente von der Warteschlange zur Festplatte spüle (es wäre in Ordnung, sie nur einer einzigen Datei anzuhängen). Sie können gelegentlich mehr als einmal dasselbe Ergebnis hinzufügen, aber dies ist nicht tödlich, da es jedes Mal das gleiche Ergebnis ist. Das Lesen von zwei oder mehr schadet sich also nicht. Das Threading -Modell von Python hält Sie aus den meisten Arten von Parallelitätsstörungen heraus (z. B. Anhang einer Liste ist atomic).

Hier sind einige (ungetestete, generische, unvollständige) Code, von denen ich spreche:

import cPickle as pickle

import time, os.path

cache = {}
queue = []

# run at script start to warm up cache
def preload_cache(filename):
    if os.path.isfile(filename):
        with open(filename, "rb") as f:
            while True:
                try:
                    key, value = pickle.load(f), pickle.load(f)
                except EOFError:
                    break
                cache[key] = value

# your memoized function
def time_consuming_function(a, b, c, d):
    key = (a, b, c, d)
    if key in cache:
        return cache[key]
    else:
        # generate the result here
        # ...
        # add to cache, checking to see if it's already there again to avoid writing
        # it twice (in case another thread also added it) (this is not fatal, though)
        if key not in cache:
            cache[key] = result
            queue.append((key, result))
        return result

# run on worker thread to write new items out
def write_cache(filename):
    with open(filename, "ab") as f:
        while True:
            while queue:
                key, value = queue.pop()  # item order not important
                # but must write key and value in single call to ensure
                # both get written (otherwise, interrupting script might
                # leave only one written, corrupting the file)
                f.write(pickle.dumps(key, pickle.HIGHEST_PROTOCOL) +
                        pickle.dumps(value, pickle.HIGHEST_PROTOCOL))
            f.flush()
            time.sleep(1)

Wenn ich Zeit hätte, würde ich dies in einen Dekorateur verwandeln ... und die Beharrlichkeit in eine dict Unterklasse ... Die Verwendung globaler Variablen ist ebenfalls suboptimal. :-) Wenn Sie diesen Ansatz mit verwenden multiprocessing Sie möchten wahrscheinlich eine verwenden multiprocessing.Queue eher als eine Liste; Sie können dann verwenden queue.get() Warten Sie als Blockierung auf ein neues Ergebnis im Arbeitsprozess, der in die Datei schreibt. Ich habe nicht benutzt multiprocessing, Nehmen Sie jedoch diesen Rat mit einem Körnchen Salz.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top