it-swarm.com.de

Verarbeiten Sie sehr große (> 20 GB) Textdateien zeilenweise

Ich habe eine Reihe sehr großer Textdateien, die ich verarbeiten muss, wobei die größte ca. 60 GB groß ist.

Jede Zeile enthält 54 Zeichen in sieben Feldern, und ich möchte die letzten drei Zeichen aus den ersten drei Feldern entfernen, wodurch die Dateigröße um etwa 20% verringert werden soll.

Ich bin ganz neu in Python und habe einen Code, der mit etwa 3,4 GB pro Stunde das macht, was ich will, aber um eine lohnende Übung zu sein, muss ich wirklich mindestens 10 GB haben/hr - Gibt es eine Möglichkeit, dies zu beschleunigen? Dieser Code ist nicht annähernd eine Herausforderung für meinen Prozessor, sodass ich ungebildet davon ausgehe, dass er durch die Lese- und Schreibgeschwindigkeit auf der internen Festplatte begrenzt ist.

def ProcessLargeTextFile():
    r = open("filepath", "r")
    w = open("filepath", "w")
    l = r.readline()
    while l:
        x = l.split(' ')[0]
        y = l.split(' ')[1]
        z = l.split(' ')[2]
        w.write(l.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))
        l = r.readline()
    r.close()
    w.close()

Jede Hilfe wäre sehr dankbar. Ich benutze die IDLE Python GUI unter Windows 7 und habe 16 GB Speicher - vielleicht wäre ein anderes Betriebssystem effizienter ?.

Edit: Hier ist ein Auszug der zu verarbeitenden Datei.

70700.642014 31207.277115 -0.054123 -1585 255 255 255
70512.301468 31227.990799 -0.255600 -1655 155 158 158
70515.727097 31223.828659 -0.066727 -1734 191 187 180
70566.756699 31217.065598 -0.205673 -1727 254 255 255
70566.695938 31218.030807 -0.047928 -1689 249 251 249
70536.117874 31227.837662 -0.033096 -1548 251 252 252
70536.773270 31212.970322 -0.115891 -1434 155 158 163
70533.530777 31215.270828 -0.154770 -1550 148 152 156
70533.555923 31215.341599 -0.138809 -1480 150 154 158
40
Tom_b

Es ist idiomatischer, Ihren Code so zu schreiben

def ProcessLargeTextFile():
    with open("filepath", "r") as r, open("outfilepath", "w") as w:
        for line in r:
            x, y, z = line.split(' ')[:3]
            w.write(line.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))

Die größte Ersparnis besteht darin, nur einmal split auszuführen. Wenn die CPU jedoch nicht besteuert wird, hat dies wahrscheinlich nur einen geringen Einfluss

Es kann helfen, ein paar tausend Zeilen auf einmal zu speichern und sie mit einem Schlag zu schreiben, um die Belastung Ihrer Festplatte zu verringern. Eine Million Zeilen sind nur 54 MB RAM!

def ProcessLargeTextFile():
    bunchsize = 1000000     # Experiment with different sizes
    bunch = []
    with open("filepath", "r") as r, open("outfilepath", "w") as w:
        for line in r:
            x, y, z = line.split(' ')[:3]
            bunch.append(line.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))
            if len(bunch) == bunchsize:
                w.writelines(bunch)
                bunch = []
        w.writelines(bunch)

vorgeschlagen von @Janne, eine alternative Möglichkeit, die Zeilen zu generieren

def ProcessLargeTextFile():
    bunchsize = 1000000     # Experiment with different sizes
    bunch = []
    with open("filepath", "r") as r, open("outfilepath", "w") as w:
        for line in r:
            x, y, z, rest = line.split(' ', 3)
            bunch.append(' '.join((x[:-3], y[:-3], z[:-3], rest)))
            if len(bunch) == bunchsize:
                w.writelines(bunch)
                bunch = []
        w.writelines(bunch)
26
John La Rooy

Messen! Sie haben einige nützliche Tipps, wie Sie Ihren python code verbessern können, und ich bin damit einverstanden. Aber Sie sollten zuerst herausfinden, was Ihr eigentliches Problem ist. Meine ersten Schritte, um Ihren Engpass zu finden, wären:

  • Entfernen Sie jegliche Verarbeitung aus Ihrem Code. Lesen und schreiben Sie einfach die Daten und messen Sie die Geschwindigkeit. Wenn nur das Lesen und Schreiben der Dateien zu langsam ist, liegt das nicht an Ihrem Code.
  • Wenn das Lesen und Schreiben bereits langsam ist, versuchen Sie, mehrere Datenträger zu verwenden. Sie lesen und schreiben gleichzeitig. Auf derselben CD? Wenn ja, versuchen Sie, andere Discs zu verwenden, und versuchen Sie es erneut.
  • Möglicherweise hilft auch eine asynchrone io-Bibliothek (Twisted?).

Wenn Sie das genaue Problem herausgefunden haben, fragen Sie erneut nach Optimierungen für dieses Problem.

12
Achim

Ich werde diese Antwort hinzufügen, um zu erklären, warum Pufferung Sinn macht und auch eine weitere Lösung anbieten

Sie bekommen eine atemberaubend schlechte Leistung. Dieser Artikel Ist es möglich, python IO? zu beschleunigen) zeigt, dass ein 10-GB-Lesevorgang in der Nähe von 3 Minuten dauern sollte. Sequentielles Schreiben entspricht der gleichen Geschwindigkeit. Sie verpassen also einen Faktor von 30 und Ihr Leistungsziel ist immer noch zehnmal langsamer als es möglich sein sollte.

Mit ziemlicher Sicherheit liegt diese Art von Disparität in der Anzahl der Kopfsuchvorgänge, die die Festplatte ausführt. Eine Kopfsuche dauert Millisekunden. Eine einzelne Suche entspricht mehreren Megabyte sequentiellem Lesen/Schreiben. Enorm teuer. Kopiervorgänge auf derselben Festplatte erfordern eine Suche zwischen Eingabe und Ausgabe. Wie bereits erwähnt, besteht eine Möglichkeit, Suchvorgänge zu reduzieren, darin, so zu puffern, dass viele Megabyte gelesen werden, bevor auf die Festplatte geschrieben wird, und umgekehrt. Wenn Sie das python io-System davon überzeugen können, ist das großartig. Andernfalls können Sie Zeilen in einem String-Array lesen und verarbeiten und dann schreiben, nachdem vielleicht 50 MB Ausgabe fertig sind. Diese Größe bedeutet a Durch das Suchen wird eine Leistungseinbuße von <10% in Bezug auf die Datenübertragung selbst hervorgerufen.

Die andere sehr einfache Möglichkeit, Suchvorgänge zwischen Eingabe- und Ausgabedateien insgesamt zu vermeiden, besteht darin, einen Computer mit zwei physischen Datenträgern und vollständig getrennten io-Kanälen für jeden zu verwenden. Eingabe von eins. Ausgabe an andere. Wenn Sie viele große Datei-Transformationen durchführen, ist es gut, eine Maschine mit dieser Funktion zu haben.

7
Gene

Da Sie nicht durch die CPU, sondern durch die E/A eingeschränkt zu sein scheinen, haben Sie es mit einigen Variationen des dritten Parameters von open versucht?

In der Tat kann dieser dritte Parameter verwendet werden, um die Puffergröße anzugeben, die für Dateioperationen verwendet werden soll!

Durch einfaches Schreiben von open( "filepath", "r", 16777216 ) werden beim Lesen aus der Datei 16 MB Puffer verwendet. Es muss helfen.

Verwenden Sie dasselbe für die Ausgabedatei und messen/vergleichen Sie es mit der identischen Datei für den Rest.

Hinweis: Dies ist die gleiche Art von Optimierung, die von anderen vorgeschlagen wurde, aber Sie können sie hier kostenlos herunterladen, ohne Ihren Code zu ändern, ohne sich selbst puffern zu müssen.

7
Didier Trosset
ProcessLargeTextFile():
    r = open("filepath", "r")
    w = open("filepath", "w")
    l = r.readline()
    while l:

Wie bereits vorgeschlagen, können Sie eine for-Schleife verwenden, um dies zu optimieren.

    x = l.split(' ')[0]
    y = l.split(' ')[1]
    z = l.split(' ')[2]

Sie führen hier dreimal einen Split-Vorgang durch. Dies wirkt sich je nach Größe der einzelnen Zeilen nachteilig auf die Leistung aus. Sie sollten einmal teilen und den Einträgen in dem Array, das zurückkommt, x, y, z zuweisen.

    w.write(l.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))

Jede Zeile, die Sie lesen, wird sofort in die Datei geschrieben, was sehr E/A-intensiv ist. Sie sollten in Betracht ziehen, Ihre Ausgabe in den Speicher zu puffern und regelmäßig auf die Festplatte zu verschieben. Etwas wie das:

BUFFER_SIZE_LINES = 1024 # Maximum number of lines to buffer in memory

def ProcessLargeTextFile():
    r = open("filepath", "r")
    w = open("filepath", "w")
    buf = ""
    bufLines = 0
    for lineIn in r:

        x, y, z = lineIn.split(' ')[:3]
        lineOut = lineIn.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3])
        bufLines+=1

        if bufLines >= BUFFER_SIZE:
            # Flush buffer to disk
            w.write(buf)
            buf = ""
            bufLines=1

        buf += lineOut + "\n"

    # Flush remaining buffer to disk
    w.write(buf)
    buf.close()
    r.close()
    w.close()

Sie können BUFFER_SIZE optimieren, um ein optimales Verhältnis zwischen Speichernutzung und Geschwindigkeit zu ermitteln.

4
seanhodges

Ihr Code ist eher unidiomatisch und ruft weit mehr Funktionen auf als benötigt. Eine einfachere Version ist:

ProcessLargeTextFile():
    with open("filepath") as r, open("output") as w:
        for line in r:
            fields = line.split(' ')
            fields[0:2] = [fields[0][:-3], 
                           fields[1][:-3],
                           fields[2][:-3]]
            w.write(' '.join(fields))

und ich kenne kein modernes Dateisystem, das langsamer als Windows ist. Haben Sie in Erwägung gezogen, eine echte Datenbank zu verwenden, da Sie diese riesigen Datendateien anscheinend als Datenbanken verwenden?

Wenn Sie nur die Dateigröße reduzieren möchten, haben Sie überlegt, die Dateien zu komprimieren/zu komprimieren?

3
msw

Das scheinen sehr große Dateien zu sein ... Warum sind sie so groß? Welche Verarbeitung machen Sie pro Zeile? Warum nicht eine Datenbank mit einigen Map Reduce-Aufrufen (falls zutreffend) oder einfachen Operationen der Daten verwenden? Der Zweck einer Datenbank besteht darin, die Verarbeitung und Verwaltung großer Datenmengen zu abstrahieren, die nicht alle in den Arbeitsspeicher passen.

Sie können mit der Idee mit sqlite beginnen, das nur flache Dateien als Datenbanken verwendet. Wenn Sie die Idee nützlich finden, aktualisieren Sie sie auf etwas Robusteres und Vielseitigeres wie postgresql .

Erstellen Sie eine Datenbank

 conn = sqlite3.connect('pts.db')
 c = conn.cursor()

Erstellt eine Tabelle

c.execute('''CREATE TABLE ptsdata (filename, line, x, y, z''')

Verwenden Sie dann einen der obigen Algorithmen, um alle Linien und Punkte durch Aufrufen in die Datenbank einzufügen

c.execute("INSERT INTO ptsdata VALUES (filename, lineNumber, x, y, z)")

Wie Sie es nun verwenden, hängt davon ab, was Sie tun möchten. Zum Beispiel, um mit allen Punkten in einer Datei zu arbeiten, indem Sie eine Abfrage ausführen

c.execute("SELECT lineNumber, x, y, z FROM ptsdata WHERE filename=file.txt ORDER BY lineNumber ASC")

Und erhalten Sie n Zeilen zu einem Zeitpunkt von dieser Abfrage mit

c.fetchmany(size=n)

Ich bin sicher, dass es irgendwo einen besseren Wrapper für die SQL-Anweisungen gibt, aber Sie haben die Idee.

2
craastad

Gibt es einen Grund, warum Sie die gezippten Dateien nicht einfach speichern können, da Sie nur das Speichern von Speicherplatz als Vorteil erwähnen? Das sollte 70% und mehr dieser Daten einsparen. Oder erwägen Sie, NTFS zu veranlassen, die Dateien zu komprimieren, wenn der wahlfreie Zugriff weiterhin wichtig ist. Sie werden viel mehr Einsparungen bei der E/A-Zeit nach einer dieser beiden erhalten.

Noch wichtiger ist, wo sind Ihre Daten, die Sie nur 3,4 GB/h erhalten? Das ist ungefähr bei USBv1-Geschwindigkeiten so.

2
jthill

Hier ist der Code zum Laden von Textdateien beliebiger Größe ohne Speicherprobleme. Es werden Dateien im Gigabyte-Format unterstützt. Es läuft problemlos auf jeder Art von Maschine, Sie müssen CHUNK_SIZE nur basierend auf Ihrem System-RAM konfigurieren. Je größer CHUNK_SIZE, desto mehr Daten werden gleichzeitig gelesen

https://Gist.github.com/iyvinjose/e6c1cb2821abd5f01fd1b9065cbc759d

laden Sie die Datei data_loading_utils.py herunter und importieren Sie sie in Ihren Code

verwendung

import data_loading_utils.py.py
file_name = 'file_name.ext'
CHUNK_SIZE = 1000000


def process_lines(line, eof, file_name):

    # check if end of file reached
    if not eof:
         # process data, data is one single line of the file

    else:
         # end of file reached

data_loading_utils.read_lines_from_file_as_data_chunks(file_name, chunk_size=CHUNK_SIZE, callback=process_lines)

Die process_lines-Methode ist die Callback-Funktion. Es wird für alle Zeilen aufgerufen, wobei die Parameterzeile jeweils eine einzelne Zeile der Datei darstellt.

Sie können die Variable CHUNK_SIZE in Abhängigkeit von den Hardwarekonfigurationen Ihrer Maschine konfigurieren.

1
Iyvin Jose

Sie können versuchen, Ihr geteiltes Ergebnis zu speichern, indem Sie es zuerst ausführen und nicht jedes Mal, wenn Sie ein Feld benötigen. Möglicherweise wird dies beschleunigen.

sie können auch versuchen, es nicht in GUI auszuführen. Führen Sie es in cmd aus.

1
Muetze

Lese die Datei mit for l in r:, um von der Pufferung zu profitieren.

1
Janne Karila