Vorab einige Erläuterungen zu meinem Standpunkt. Codeschnipsel sind am Ende.
Wenn ich kann, verwende ich lieber ein Open-Source-Tool wie H2O, um parallele CSV-Dateien mit hoher Leistung zu lesen, aber dieses Tool hat nur einen begrenzten Funktionsumfang. Am Ende schreibe ich eine Menge Code, um Data Science Pipelines zu erstellen, bevor ich den H2O-Cluster für das eigentliche überwachte Lernen füttere.
Ich habe Dateien wie den 8 GB großen HIGGS-Datensatz aus dem UCI-Repository und sogar 40 GB große CSV-Dateien für datenwissenschaftliche Zwecke deutlich schneller gelesen, indem ich mit dem Pool-Objekt und der Map-Funktion der Multiprocessing-Bibliothek viel Parallelität hinzugefügt habe. So erfordert beispielsweise das Clustering mit Nearest Neighbour Searches sowie DBSCAN- und Markov-Clustering-Algorithmen eine gewisse Finesse bei der parallelen Programmierung, um einige schwierige Speicher- und Wanduhrzeitprobleme zu umgehen.
Normalerweise zerlege ich die Datei zeilenweise in Teile, indem ich zuerst die Gnu-Tools verwende, und dann werden sie alle mit glob-filemask gefunden und parallel im Python-Programm gelesen. Ich verwende in der Regel etwa 1000+ Teildateien. Diese Tricks helfen ungemein bei der Verarbeitungsgeschwindigkeit und der Speicherbegrenzung.
Der Pandas dataframe.read_csv ist single threaded, so dass Sie diese Tricks anwenden können, um Pandas schneller zu machen, indem Sie eine map() für die parallele Ausführung ausführen. Sie können htop verwenden, um zu sehen, dass bei der einfachen sequenziellen Ausführung von pandas dataframe.read_csv 100 % CPU-Leistung auf nur einem Kern der eigentliche Engpass in pd.read_csv ist, nicht die Festplatte.
Ich sollte hinzufügen, dass ich eine SSD am schnellen Grafikkartenbus verwende, keine Spinning HD am SATA6-Bus, plus 16 CPU-Kerne.
Eine weitere Technik, die sich in einigen Anwendungen bewährt hat, ist das parallele Lesen von CSV-Dateien innerhalb einer riesigen Datei, wobei jeder Worker an einem anderen Offset in der Datei beginnt, anstatt eine große Datei in viele Teildateien aufzuteilen. Verwenden Sie Python's file seek() und tell() in jedem parallelen Worker, um die große Textdatei in Streifen zu lesen, an verschiedenen Byte-Offset-Start-Byte und End-Byte-Positionen in der großen Datei, alle zur gleichen Zeit, gleichzeitig. Sie können einen Regex findall auf die Bytes anwenden und die Anzahl der Zeilenumbrüche zurückgeben. Dies ist eine Teilsumme. Zum Schluss werden die Teilsummen addiert, um die Gesamtsumme zu erhalten, wenn die Map-Funktion zurückkehrt, nachdem die Arbeiter fertig sind.
Nachfolgend finden Sie einige Beispiel-Benchmarks, die den Trick des parallelen Byte-Offsets verwenden:
Ich verwende 2 Dateien: HIGGS.csv ist 8 GB groß. Sie stammt aus dem UCI-Repository für maschinelles Lernen. all_bin.csv ist 40,4 GB groß und stammt aus meinem aktuellen Projekt. Ich verwende 2 Programme: Das GNU wc-Programm, das mit Linux geliefert wird, und das reine Python-Programm fastread.py, das ich entwickelt habe.
HP-Z820:/mnt/fastssd/fast_file_reader$ ls -l /mnt/fastssd/nzv/HIGGS.csv
-rw-rw-r-- 1 8035497980 Jan 24 16:00 /mnt/fastssd/nzv/HIGGS.csv
HP-Z820:/mnt/fastssd$ ls -l all_bin.csv
-rw-rw-r-- 1 40412077758 Feb 2 09:00 all_bin.csv
ga@ga-HP-Z820:/mnt/fastssd$ time python fastread.py --fileName="all_bin.csv" --numProcesses=32 --balanceFactor=2
2367496
real 0m8.920s
user 1m30.056s
sys 2m38.744s
In [1]: 40412077758. / 8.92
Out[1]: 4530501990.807175
Das sind 4,5 GB/s bzw. 45 Gb/s, eine Geschwindigkeit, mit der Dateien geschlürft werden. Das ist keine rotierende Festplatte, mein Freund. Es handelt sich um eine Samsung Pro 950 SSD.
Unten sehen Sie den Geschwindigkeits-Benchmark für dieselbe Datei, die von gnu wc, einem rein in C kompilierten Programm, gezählt wird.
Cool ist, dass mein reines Python-Programm in diesem Fall im Wesentlichen mit der Geschwindigkeit des mit gnu wc kompilierten C-Programms mithalten kann. Python ist interpretiert, aber C ist kompiliert, also ist dies eine ziemlich interessante Leistung in Bezug auf die Geschwindigkeit, ich denke, Sie werden zustimmen. Natürlich muss wc wirklich zu einem parallelen Programm geändert werden, und dann würde es mein Python-Programm wirklich von den Socken hauen. Aber so wie es heute aussieht, ist gnu wc nur ein sequentielles Programm. Man tut, was man kann, und Python kann heute parallel arbeiten. Das Kompilieren von Cython könnte mir vielleicht helfen (für ein anderes Mal). Auch memory mapped files wurden noch nicht erforscht.
HP-Z820:/mnt/fastssd$ time wc -l all_bin.csv
2367496 all_bin.csv
real 0m8.807s
user 0m1.168s
sys 0m7.636s
HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000
real 0m2.257s
user 0m12.088s
sys 0m20.512s
HP-Z820:/mnt/fastssd/fast_file_reader$ time wc -l HIGGS.csv
11000000 HIGGS.csv
real 0m1.820s
user 0m0.364s
sys 0m1.456s
Schlussfolgerung: Die Geschwindigkeit ist für ein reines Python-Programm im Vergleich zu einem C-Programm gut. Es ist jedoch nicht gut genug, um das reine Python-Programm dem C-Programm vorzuziehen, zumindest für den Zweck der Zeilenzählung. Im Allgemeinen kann die Technik auch für andere Dateiverarbeitungen verwendet werden, so dass dieser Python-Code immer noch gut ist.
Frage: Wird die Geschwindigkeit erhöht, wenn die Regex nur einmal kompiliert und an alle Worker übergeben wird? Antwort: Ja: Das Vorkompilieren von Regex hilft in dieser Anwendung NICHT. Ich nehme an, der Grund ist, dass der Overhead der Prozessserialisierung und -erstellung für alle Worker dominiert.
Noch eine Sache. Ist das parallele Lesen von CSV-Dateien überhaupt hilfreich? Ist die Festplatte der Engpass, oder ist es die CPU? Viele so genannte Top-Rating-Antworten auf Stackoverflow enthalten die gängige Entwicklerweisheit, dass man nur einen Thread zum Lesen einer Datei braucht, das Beste, was man tun kann. Sind sie sich aber sicher?
Finden wir es heraus:
HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000
real 0m2.256s
user 0m10.696s
sys 0m19.952s
HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=1
11000000
real 0m17.380s
user 0m11.124s
sys 0m6.272s
Oh ja, das tut es. Das parallele Lesen von Dateien funktioniert sehr gut. Na also, geht doch!
Ps. Falls einige von euch wissen wollen, was passiert, wenn der balanceFactor bei Verwendung eines einzelnen Worker-Prozesses 2 ist? Nun, das ist furchtbar:
HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=2
11000000
real 1m37.077s
user 0m12.432s
sys 1m24.700s
Die wichtigsten Teile des fastread.py Py-Programms:
fileBytes = stat(fileName).st_size # Read quickly from OS how many bytes are in a text file
startByte, endByte = PartitionDataToWorkers(workers=numProcesses, items=fileBytes, balanceFactor=balanceFactor)
p = Pool(numProcesses)
partialSum = p.starmap(ReadFileSegment, zip(startByte, endByte, repeat(fileName))) # startByte is already a list. fileName is made into a same-length list of duplicates values.
globalSum = sum(partialSum)
print(globalSum)
def ReadFileSegment(startByte, endByte, fileName, searchChar='\n'): # counts number of searchChar appearing in the byte range
with open(fileName, 'r') as f:
f.seek(startByte-1) # seek is initially at byte 0 and then moves forward the specified amount, so seek(5) points at the 6th byte.
bytes = f.read(endByte - startByte + 1)
cnt = len(re.findall(searchChar, bytes)) # findall with implicit compiling runs just as fast here as re.compile once + re.finditer many times.
return cnt
Die Def für PartitionDataToWorkers ist ein ganz normaler sequentieller Code. Ich habe ihn weggelassen, für den Fall, dass jemand etwas Übung in paralleler Programmierung haben möchte. Die schwierigeren Teile habe ich kostenlos abgegeben: den getesteten und funktionierenden parallelen Code, damit Sie etwas lernen können.
Dank an: Das Open-Source-Projekt H2O von Arno und Cliff und den H2O-Mitarbeitern für ihre großartige Software und die Anleitungsvideos, die mich zu dem oben gezeigten hochleistungsfähigen parallelen Byte-Offset-Reader in Python inspiriert haben. H2O liest parallele Dateien mit Java, kann von Python und R-Programmen aufgerufen werden und ist wahnsinnig schnell, schneller als alles andere auf der Welt beim Lesen großer CSV-Dateien.