Wie verwendet man große Dicts in Python, die nicht in den Speicher passen? - Python, Speicher, Datenstrukturen, Wörterbuch

Wir benutzen ein dict Das enthält ca. 4 GB Daten für die Datenverarbeitung. Es ist bequem und schnell.

Das Problem, das wir haben, ist das dict könnte über 32 GB wachsen.

Ich suche nach einer Möglichkeit, ein dict (genau wie eine Variable mit get () - Methode usw.), die größer als der verfügbare Speicher sein kann. Es wäre großartig, wenn das so wäre dict irgendwie die Daten auf der Festplatte gespeichert und die Daten von der Festplatte abgerufen, wenn get(key) heißt und Wert für die key ist nicht im Speicher.

Vorzugsweise möchte ich keinen externen Dienst wie eine SQL-Datenbank verwenden.

Ich habe es gefunden Ablegen, aber es scheint auch die Erinnerung zu brauchen.

Irgendwelche Ideen, wie man dieses Problem angeht?

Antworten:

3 für die Antwort № 1

Das klingt, als könnten Sie einen Schlüssel-Wert-Speicher verwenden, der gerade unter dem Schlagwort No-SQL gehypt ist. Eine gute Einführung dazu findet sich beispielsweise in

http://ayende.com/blog/4449/that-no-sql-thing-key-value-stores.

Es ist einfach eine Datenbank mit der von Ihnen beschriebenen API.


1 für die Antwort № 2

Wenn Sie keine SQL-Datenbank verwenden möchten (was eine vernünftige Lösung für ein Problem wie dieses ist), müssen Sie entweder einen Weg finden, die Daten zu komprimieren, mit denen Sie arbeiten oder die Sie verwenden eine Bibliothek wie diese (oder Ihre eigenen), um das Mapping zu machen, um selbst zu disc

Sie können auch schauen diese Frage für einige weitere Strategien.


1 für die Antwort № 3

Benutze die Essiggurke Modul zum Serialisieren des Wörterbuchs auf die Festplatte.Nimm dann aufeinanderfolgende Werte aus dem Iterator des Wörterbuchs und lege sie zunächst in deinen Cache. Implementiere dann ein Cache-Schema wie LRU; Entfernen Sie ein Dictionary-Element mit der `popitem () -Methode von Dictionaries und fügen Sie im Fall von LRU das zuvor aufgerufene Element hinzu.


1 für die Antwort № 4

Ich konnte kein (schnelles) Modul dafür finden und entschied mich mein eigenes (mein erstes Python-Projekt - danke @blubber für einige Ideen: P) zu erstellen. Du findest es auf GitHub: https://github.com/ddofborg/diskdict Kommentare sind willkommen!


0 für die Antwort № 5

/ EDIT: Dies ist jetzt ein Python-Modul: Urteil.

Ich hatte ein ähnliches Problem, aber ich arbeitete an verschachtelten Diktaten. Aufgrund der sehr verschachtelten Natur meines Datasets würden alle verfügbaren Lösungen nicht zu meinem Anwendungsfall passen: shelve, chest, shove, sqlite, zodbund jede andere NoSQL-Datenbank funktioniert hauptsächlich bei Ihnenhaben eine Ebene, weil sie alle / JSON die Werte serialisieren, die in die Datenbank serialisiert werden, so dass alle Werte nach der ersten Ebene serialisiert werden und es nicht möglich ist, inkrementelle Aktualisierung der verschachtelten Objekte durchzuführen (Sie müssen den gesamten Zweig von der ersten laden -level node, modifizieren und dann zurückstellen), was nicht möglich ist, wenn Ihre verschachtelten Objekte selbst riesig sind.

Dies bedeutet, dass diese Lösungen hilfreich sind, wenn Sie viele Knoten der 1. Ebene haben, aber nicht, wenn Sie einige Knoten der 1. Ebene und eine tiefe Hierarchie haben.

Um dem entgegenzuwirken, habe ich eine benutzerdefinierte dict-Klasse über das native Python-Diktat erstellt, um das verschachtelte dict in einer abgeflachten Form intern darzustellen: dict()["a"]["b"] wird intern als dargestellt werden dict()["a/b"].

Dann, zusätzlich zu diesem "intern abgeflachten" dict, können Sie jetzt jedes Objekt zur Datenträgerlösung verwenden shelve, was ich benutzt habe, aber du kannst es leicht durch etwas anderes ersetzen.

Hier ist der Code:

import shelve

class fdict(dict):
"""Flattened nested dict, all items are settable and gettable through ["item1"]["item2"] standard form or ["item1/item2"] internal form.
This allows to replace the internal dict with any on-disk storage system like a shelve"s shelf (great for huge nested dicts that cannot fit into memory).
Main limitation: an entry can be both a singleton and a nested fdict, and there is no way to tell what is what, no error will be shown, the singleton will always be returned.
"""
def __init__(self, d=None, rootpath="", delimiter="/", *args):
if d:
self.d = d
else:
self.d = {}
self.rootpath = rootpath
self.delimiter = delimiter

def _buildpath(self, key):
return self.rootpath+self.delimiter+key if self.rootpath else key

def __getitem__(self, key):
# Node or leaf?
if key in self.d: # Leaf: return the value
return self.d.__getitem__(key)
else: # Node: return a new full fdict based on the old one but with a different rootpath to limit the results by default
return fdict(d=self.d, rootpath=self._buildpath(key))

def __setitem__(self, key, value):
self.d.__setitem__(self._buildpath(key), value)

def keys(self):
if not self.rootpath:
return self.d.keys()
else:
pattern = self.rootpath+self.delimiter
lpattern = len(pattern)
return [k[lpattern:] for k in self.d.keys() if k.startswith(pattern)]

def items(self):
# Filter items to keep only the ones below the rootpath level
if not self.rootpath:
return self.d.items()
else:
pattern = self.rootpath+self.delimiter
lpattern = len(pattern)
return [(k[lpattern:], v) for k,v in self.d.items() if k.startswith(pattern)]

def values(self):
if not self.rootpath:
return self.d.values()
else:
pattern = self.rootpath+self.delimiter
lpattern = len(pattern)
return [v for k,v in self.d.items() if k.startswith(pattern)]

def update(self, d2):
return self.d.update(d2.d)

def __repr__(self):
# Filter the items if there is a rootpath and return as a new fdict
if self.rootpath:
return repr(fdict(d=dict(self.items())))
else:
return self.d.__repr__()

def __str__(self):
if self.rootpath:
return str(fdict(d=dict(self.items())))
else:
return self.d.__str__()

class sfdict(fdict):
"""A nested dict with flattened internal representation, combined with shelve to allow for efficient storage and memory allocation of huge nested dictionnaries.
If you change leaf items (eg, list.append), do not forget to sync() to commit changes to disk and empty memory cache because else this class has no way to know if leaf items were changed!
"""
def __init__(self, *args, **kwargs):
if not ("filename" in kwargs):
self.filename = None
else:
self.filename = kwargs["filename"]
del kwargs["filename"]
fdict.__init__(self, *args, **kwargs)
self.d = shelve.open(filename=self.filename, flag="c", writeback=True)

def __setitem__(self, key, value):
fdict.__setitem__(self, key, value)
self.sync()

def get_filename(self):
return self.filename

def sync(self):
self.d.sync()

def close(self):
self.d.close()

Beide fdict und sfdict sind wie Standard verwendbar dict (aber ich habe nicht alle Methoden implementiert).

Der vollständige Code ist hier: https://gist.github.com/lrq3000/8ce9174c1c7a5ef546df1e1361417213

Dies wurde in einem vollständigen Python-Modul weiterentwickelt: Urteil.

Nach dem Benchmarking ist dies etwa 10x langsamer als ein Diktat bei Verwendung des indirekten Zugriffs (dh x["a"]["b"]["c"]), und ungefähr so ​​schnell, wenn man direkten Zugang benutzt (dh x["a/b/c"]), obwohl ich hier den Overhead nicht berücksichtige shelve Speichern in a anydbm Datei, nur von fdict Datenstruktur im Vergleich zu dict.


Speisekarte