2010-09-26 18 views
6

Ich bin ein komplette Anfänger Python oder eine andere ernsthafte Programmiersprache für diese Angelegenheit. Ich habe endlich einen Prototyp Code zum arbeiten, aber ich denke, es wird zu langsam sein.Optimieren suchen und ersetzen über große Dateien in Python

Mein Ziel ist es, einige chinesische Zeichen über alle Dateien (sie sind csv) in einem Verzeichnis mit Ganzzahlen nach einer CSV-Datei zu finden und zu ersetzen. Die Dateien sind schön nach Jahr-Monat nummeriert, zum Beispiel 2000-01.csv, und sind die einzigen Dateien in diesem Verzeichnis.

Ich werde über etwa 25 Dateien in der Nähe von 500mb (und etwa eine Million Zeilen) loopen. Das Wörterbuch, das ich benutzen werde, wird ungefähr 300 Elemente haben und ich werde Unicode (chinesisches Zeichen) in Ganzzahlen ändern. Ich habe es mit einem Testlauf versucht, und wenn alles linear ansteigt (?), Sieht es so aus, als würde es ungefähr eine Woche dauern, bis dies ausgeführt wird.

Vielen Dank im Voraus. Hier ist mein Code (nicht lachen!):

# -*- coding: utf-8 -*- 

import os, codecs 

dir = "C:/Users/Roy/Desktop/test/" 

Dict = {'hello' : 'good', 'world' : 'bad'} 

for dirs, subdirs, files in os.walk(dir): 
    for file in files: 
     inFile = codecs.open(dir + file, "r", "utf-8") 
     inFileStr = inFile.read() 
     inFile.close() 
     inFile = codecs.open(dir + file, "w", "utf-8") 
     for key in Dict: 
      inFileStr = inFileStr.replace(key, Dict[key]) 
     inFile.write(inFileStr) 
     inFile.close() 
+0

Es ist die Python-Konvention, Instanzvariablen mit Kleinbuchstaben zu benennen. Ich würde auch das Wort "Dict" durch etwas anderes als den Typ ersetzen, um zukünftige Verwirrung zu vermeiden. –

+0

Besteht Ihr Wörterbuchschlüssel aus genau einem chinesischen Zeichen, oder sind mehrere Zeichen pro Schlüssel möglich? Warum möchten Sie chinesische Zeichen durch Ganzzahlen ersetzen? –

+0

@John: Ich habe noch 35 Dateien, die diese Information bereits mit ganzen Zahlen codiert haben, und ich werde meine Analyse in Stata machen, die nicht Unicode liest. Ich muss mehrere Zeichen gleichzeitig lesen, nicht nur 1. – rallen

Antwort

13

In Ihrem aktuellen Code lesen Sie die gesamte Datei gleichzeitig in den Speicher. Da es sich um 500 MB-Dateien handelt, sind das 500 MB-Strings. Und dann wiederholst du sie, was bedeutet, dass Python eine neue 500Mb-Zeichenkette mit der ersten Ersetzung erstellen muss, dann die erste Zeichenkette zerstören, dann eine zweite 500Mb-Zeichenkette für die zweite Ersetzung erstellen und dann die zweite Zeichenkette zerstören muss, und so weiter. für jeden Ersatz. Es stellt sich heraus, dass das Kopieren von Daten hin und her sehr viel ist, ganz zu schweigen von der Verwendung von viel Speicher.

Wenn Sie wissen, dass die Ersetzungen immer in einer Zeile enthalten sein werden, können Sie die Datei Zeile für Zeile lesen, indem Sie darüber iterieren. Python puffert das Lesen, was bedeutet, dass es ziemlich optimiert wird. Sie sollten eine neue Datei unter einem neuen Namen öffnen, um die neue Datei gleichzeitig zu schreiben. Führen Sie den Austausch nacheinander in jeder Zeile durch und schreiben Sie ihn sofort aus.Dadurch wird erheblich die Menge des verwendeten Speichers und die Menge an Speichern hin und her kopiert reduzieren, wie Sie den Ersatz tun:

for file in files: 
    fname = os.path.join(dir, file) 
    inFile = codecs.open(fname, "r", "utf-8") 
    outFile = codecs.open(fname + ".new", "w", "utf-8") 
    for line in inFile: 
     newline = do_replacements_on(line) 
     outFile.write(newline) 
    inFile.close() 
    outFile.close() 
    os.rename(fname + ".new", fname) 

Wenn Sie nicht sicher sein können, wenn sie immer auf einer Linie sein werden , die Dinge werden etwas härter; Sie müssten Blöcke manuell unter Verwendung von inFile.read(blocksize) einlesen und sorgfältig prüfen, ob am Ende der Blockade eine teilweise Übereinstimmung besteht. Nicht so einfach zu machen, aber in der Regel lohnt es sich immer noch die 500Mb Saiten zu vermeiden.

Eine weitere große Verbesserung wäre, wenn Sie die Ersetzungen auf einmal durchführen könnten, statt eine ganze Reihe von Ersatz in der Reihenfolge zu versuchen. Es gibt mehrere Möglichkeiten, das zu tun, aber was am besten passt, hängt ganz davon ab, was Sie ersetzen und womit. Um einzelne Zeichen in etwas anderes zu übersetzen, kann die translate Methode der Unicode-Objekte bequem sein. Sie geben ihm ein dict Mapping Unicode-Codepoints (als ganze Zahlen) auf Unicode-Strings:

>>> u"\xff and \ubd23".translate({0xff: u"255", 0xbd23: u"something else"}) 
u'255 and something else' 

Für Teil (und nicht nur einzelne Zeichen) zu ersetzen, können Sie die re Modul nutzen könnten. Die re.sub Funktion (und die sub Methode kompilierter regexps) können eine aufrufbare nehmen (eine Funktion) als erstes Argument, die dann für jedes Spiel aufgerufen werden:

>>> import re 
>>> d = {u'spam': u'spam, ham, spam and eggs', u'eggs': u'saussages'} 
>>> p = re.compile("|".join(re.escape(k) for k in d)) 
>>> def repl(m): 
...  return d[m.group(0)] 
... 
>>> p.sub(repl, u"spam, vikings, eggs and vikings") 
u'spam, ham, spam and eggs, vikings, saussages and vikings' 
+0

Ich hatte vergessen, nicht änderbare Zeichenfolge.Viel schöner als meine Antwort. – aaronasterling

+2

Ich wollte Ihrer Antwort hinzufügen, dass die 500Mb-Zeichenfolge nicht nur eine Frage des Einpassens in RAM oder des Swap-Testens ist, sondern auch, wie die meisten Architekturen mit wiederholten Operationen auf einer kleineren Datenmenge besser umgehen können die CPU puffert gut, obwohl Python den Cache schnell mit seinen eigenen Sachen füllt.) Darüber hinaus optimiert Python auch die Zuordnung von kleineren Objekten zu großen Objekten, was insbesondere unter Windows wichtig ist (aber davon profitieren alle Plattformen) Grad.) –

+0

Wenn Sie die Ausgabedateien auf einer anderen physischen Festplatte suchen, wird die gesamte Prozedur wahrscheinlich schneller ausgeführt, da der Engpass beim Lesen von und Schreiben auf die Festplatte liegt. Sie könnten die Leistung wahrscheinlich weiter verbessern, indem Sie die Schreibvorgänge in einem separaten Thread ausführen und jede Zeile über eine 'Queue.Queue' an diese übergeben. Ich denke, dass die Nützlichkeit dieses letzten Maßes von der Effektivität des Readahead-Cache des Lesetreibers in Kombination mit irgendeinem Schreib-Caching auf dem Schreiblaufwerk abhängen würde. Aber das ist vielleicht auch etwas zu schwer für einen Python-Anfänger. – intuited

0

Öffnen Sie die Dateien lesen/schreiben (‚r +‘) und zur Vermeidung der doppelten Öffnen/Schließen (und wahrscheinlich Puffer bündig verbunden ist). Wenn möglich, schreiben Sie nicht die gesamte Datei zurück, suchen und schreiben Sie nur die geänderten Bereiche zurück, nachdem Sie den Inhalt der Datei ersetzt haben. Lesen, ersetzen, geänderte Bereiche schreiben (falls vorhanden).

Das hilft immer noch nicht Leistung auch viel aber: Ich würde Profil und bestimmen, wo die Leistung tatsächlich ist und dann auf die Optimierung gehen. Es könnte nur das Lesen der Daten von der Festplatte sein, das sehr langsam ist, und es gibt nicht viel, was Sie in Python dagegen tun können.

+1

'rw' ist nicht 'lesen/schreiben'. Es wird nur 'gelesen', da das 'w' vollständig ignoriert wird. Die Modi für 'Lesen/Schreiben' sind 'r +', 'w +' und 'a +', wobei jeder etwas ganz anderes macht. Das Umschreiben einer Datei während des Lesens ist schwierig, da zwischen Lese- und Schreibvorgängen gesucht werden muss und Sie darauf achten müssen, dass Sie nicht überschreiben, was Sie noch nicht gelesen haben. –

+0

@Thomas: Ah, ja. Immer auf den offenen() Flaggen erwischt werden. Zu viel C :). Wie auch immer, mein Vorschlag war, die Datei zuerst vollständig zu lesen und dann nur Änderungen zurückzuschreiben, Änderungen nicht während des Lesens zurückzuschreiben. –

+1

Die Zeichenfolge, die du an open() übergibst, ist eigentlich das, was du an "fopen()" in C weitergeben würdest (und warum es so eine sucky Semantik hat), also "zu viel C" ist keine Entschuldigung :-) –

1

Ein paar Dinge (in keinem Zusammenhang mit dem Optimierungsproblem):

dir + file sollte os.path.join(dir, file)

Sie möchten vielleicht nicht infile wieder zu verwenden, sondern offen (und schreibt) eine separate Ausgabedatei. Dies erhöht auch nicht die Leistung, ist aber eine gute Vorgehensweise.

Ich weiß nicht, ob Sie I/O-gebunden oder CPU-gebunden sind, aber wenn Ihre CPU-Auslastung sehr hoch ist, möchten Sie möglicherweise Threading verwenden, wobei jeder Thread in einer anderen Datei arbeitet (also mit einem Quad Core Prozessor, würden Sie 4 verschiedene Dateien gleichzeitig lesen/schreiben).

+0

Sie haben den Einfädelhinweis komplett rückwärts. In Python thread, um IO-Grenzen zu umgehen. Dies liegt an der Global Interpreter Lock. Sie verwenden Subprozesse für CPU/Speicher beschränkte Anwendungen, was das ist. (nur 50 IO-Operationen in einer Woche;) – aaronasterling

+0

Guter Punkt. Ich wusste von der globalen Sperre, dachte aber nicht wirklich über Teilprozesse vs. Threads nach. Jeden Tag etwas Neues lernen. – babbitt

+0

@AaronMcSmooth: Ich würde erwarten, dass dies E/A-gebunden ist, da die Suche nach einem String und Ersetzen von einem Wörterbuch für einen modernen Prozessor ziemlich mühsam ist. Aber in diesem Fall ist Multithreading nicht hilfreich, es sei denn, einige der Dateien befinden sich auf separaten physischen Festplatten oder es ist möglich, die übersetzten Dateien auf einer anderen physischen Festplatte zu finden. – intuited

2

Ich glaube, Sie Speicherverbrauch stark senken kann (und damit die Verwendung von Swaps begrenzen und Dinge schneller machen), indem Sie jeweils eine Zeile lesen und sie (nach den bereits vorgeschlagenen Regexp-Ersetzungen) in eine temporäre Datei schreiben und dann die Datei verschieben, um das Original zu ersetzen.

Verwandte Themen