2013-07-17 11 views
7

Ich muss einige Daten verarbeiten, die ein paar hundert Mal größer als RAM ist. Ich möchte in einem großen Stück lesen, verarbeiten, speichern Sie das Ergebnis, befreien Sie die Erinnerung und wiederholen. Gibt es eine Möglichkeit, dies in Python effizient zu machen?Verarbeite große Daten in Python

+2

Mögliche Duplikate: http://StackOverflow.com/Questions/519633/Lacy-Method-for-Reading-Big-File-in-Python –

+3

Überprüfen Sie Pandas und Pytables/HDF oder Hadoop Streaming mit Python. Wenn Sie unter Linux können Sie dumbo die hadoop Python-Interaktion erleichtern. Python hat eine starke und dynamische Community für die Datenanalyse. Es ist schwer mit einer Google-Suche zu verpassen. – agconti

+0

Nicht ein dup, aber auch verwandt: [Python-Datei-Iterator über eine Binärdatei mit neuerem Idiom] (http://stackoverflow.com/questions/4566498/python-file-iterator-over-a-binary-file-with- neuere-idiom/4566523 # 4566523). – abarnert

Antwort

19

Der allgemeine Schlüssel ist, dass Sie die Datei iterativ verarbeiten möchten.

Wenn Sie nur mit einer Textdatei zu tun haben, ist dies trivial: for line in f: liest nur in einer Zeile zu einer Zeit. (Tatsächlich puffert es die Dinge auf, aber die Puffer sind klein genug, dass Sie sich darüber keine Gedanken machen müssen.)

Wenn Sie mit einem anderen spezifischen Dateityp arbeiten, z. B. einer numpligen Binärdatei, einer CSV-Datei , ein XML-Dokument, usw., gibt es im Allgemeinen ähnliche spezielle Lösungen, aber niemand kann sie Ihnen beschreiben, wenn Sie uns nicht sagen, welche Art von Daten Sie haben.

Aber was ist, wenn Sie eine allgemeine Binärdatei haben?


Zuerst wird die read Methode nimmt ein optional max gelesenen Bytes. Anstatt also diese:

data = f.read() 
process(data) 

Sie können dies tun:

while True: 
    data = f.read(8192) 
    if not data: 
     break 
    process(data) 

Vielleicht möchten Sie stattdessen eine Funktion wie folgt schreiben:

def chunks(f): 
    while True: 
     data = f.read(8192) 
     if not data: 
      break 
     yield data 

Dann können Sie einfach tun Sie dies:

for chunk in chunks(f): 
    process(chunk) 

Sie könnten dies auch tun, mit dem Zwei-Argumente iter, aber viele Leute finden, dass ein bisschen dunkel:

for chunk in iter(partial(f.read, 8192), b''): 
    process(chunk) 

So oder so, diese Option zu allen anderen Varianten unten (mit Ausnahme eines einzigen gilt mmap , das ist trivial genug, dass es keinen Sinn hat).


Es gibt nichts Magisches über die Nummer 8192 dort. Im Allgemeinen möchten Sie eine Potenz von 2 und idealerweise ein Vielfaches der Seitengröße Ihres Systems. Darüber hinaus variiert Ihre Leistung nicht so sehr, unabhängig davon, ob Sie 4KB oder 4MB verwenden. Wenn dies der Fall ist, müssen Sie testen, was für Ihren Anwendungsfall am besten geeignet ist.

Wie auch immer, das geht davon aus, dass Sie nur jede 8K auf einmal verarbeiten können, ohne irgendeinen Kontext zu halten. Wenn Sie beispielsweise Daten in einen progressiven Decoder oder Hasher oder etwas einspeisen, ist das perfekt.

Wenn Sie jedoch einen "Chunk" gleichzeitig verarbeiten müssen, könnten Ihre Chunks über eine 8-KB-Grenze hinausreichen. Wie gehst du damit um?

Es hängt davon ab, wie Ihre Chunks in der Datei begrenzt sind, aber die Grundidee ist ziemlich einfach. Angenommen, Sie verwenden NUL-Bytes als Trennzeichen (nicht sehr wahrscheinlich, aber einfach als Spielzeugbeispiel zu zeigen).

data = b'' 
while True: 
    buf = f.read(8192) 
    if not buf: 
     process(data) 
     break 
    data += buf 
    chunks = data.split(b'\0') 
    for chunk in chunks[:-1]: 
     process(chunk) 
    data = chunks[-1] 

Diese Art von Code ist sehr häufig in Netzwerken (weil socketsnicht nur „alles lesen“, so dass Sie immer in einen Puffer und Brocken in Nachrichten lesen), so können Sie finden Sie einige nützliche Beispiele in Netzwerkcode, der ein Protokoll ähnlich Ihrem Dateiformat verwendet.


Alternativ können Sie mmap verwenden.

Wenn die virtuelle Speichergröße größer ist als die Datei, das ist trivial:

with mmap.mmap(f.fileno(), access=mmap.ACCESS_READ) as m: 
    process(m) 

Jetzt m wirkt wie ein riesiges bytes Objekt, als ob Sie read() angerufen hatten die ganze Sache in dem Speicher zu lesen - aber das Betriebssystem wird die Bits bei Bedarf automatisch ein- und ausblenden.


Wenn Sie versuchen, eine Datei zu groß, um fit in der Größe der virtuellen Speichers zu lesen (zB eine 4 GB-Datei mit 32-Bit-Python oder eine 20eB Datei mit 64-Bit-Python-das ist nur Wahrscheinlich passiert 2013, wenn Sie eine spärliche oder virtuelle Datei lesen, wie zum Beispiel die VM-Datei für einen anderen Prozess unter Linux.) Sie müssen windowing-mmap in einem Teil der Datei gleichzeitig implementieren. Zum Beispiel:

windowsize = 8*1024*1024 
size = os.fstat(f.fileno()).st_size 
for start in range(0, size, window size): 
    with mmap.mmap(f.fileno(), access=mmap.ACCESS_READ, 
        length=windowsize, offset=start) as m: 
     process(m) 

Natürlich Mapping-Fenster hat das gleiche Problem wie Stücke zu lesen, wenn Sie die Dinge abgrenzen müssen, und Sie können es auf die gleiche Weise zu lösen.

Aber als eine Optimierung, anstatt Pufferung, können Sie einfach schieben Sie das Fenster auf die Seite mit dem Ende der letzten vollständigen Nachricht statt 8MB auf einmal, und dann können Sie vermeiden, kopieren. Dies ist ein bisschen komplizierter, wenn Sie es tun wollen, suchen Sie nach etwas wie "sliding mmap window" und schreiben Sie eine neue Frage, wenn Sie nicht weiterkommen.

+1

Ich empfehle Ihnen, eine so durchdachte Antwort auf eine so umfassende Frage zu geben. Ernsthaft, +1. – 2rs2ts

+0

Danke! In meinem Fall hätte ich aus Effizienzgründen gerne ein Stück RAM. Können Sie das ohne Versuch und Irrtum tun? – marshall

+0

@marshall: Sie wollen nicht wirklich die Größe von (physischem) RAM haben, weil ein Teil dieses RAM vom Rest Ihres Interpreterraums, des Kernels, anderer Prozesse, des Plattencaches usw. benötigt wird. Sobald Sie das Stück groß genug haben, gibt es nicht viel mehr zu gewinnen; Wenn Ihr Code mit den Datenträger-DMAs so nah wie möglich an den Pipelines arbeitet, können größere Lesevorgänge nicht helfen. Sie können (und sollten) es selbst testen, aber normalerweise liegt der Sweet Spot irgendwo zwischen 4KB und 8MB, nicht irgendwo in der Nähe des physikalischen Speichers. – abarnert