Pewien kontekst z góry, skąd pochodzę. Fragmenty kodu znajdują się na końcu.
Kiedy mogę, wolę używać narzędzia typu open source, takiego jak H2O, do robienia bardzo wysokiej wydajności równoległych odczytów plików CSV, ale to narzędzie ma ograniczony zestaw funkcji. W końcu piszę dużo kodu do tworzenia potoków nauki danych przed przekazaniem do klastra H2O w celu właściwego nadzorowanego uczenia się.
Czytam pliki takie jak 8 GB zestawu danych HIGGS z repozytorium UCI, a nawet 40 GB plików CSV do celów analizy danych znacznie szybciej, dodając dużo równoległości z obiektem puli i funkcją mapy biblioteki wieloprocesowej. Na przykład grupowanie z wyszukiwaniem najbliższych sąsiadów, a także algorytmy klastrowania DBSCAN i Markowa wymagają pewnej finezji programowania równoległego, aby ominąć niektóre poważnie trudne problemy z pamięcią i czasem zegara ściennego.
Zwykle lubię łamać plik wierszowo na części za pomocą narzędzi GNU, a następnie glob-filemaskować je wszystkie, aby znaleźć i odczytać je równolegle w programie python. Często używam ponad 1000 plików częściowych. Wykonanie tych sztuczek niezwykle pomaga w ograniczeniu prędkości przetwarzania i pamięci.
Panda dataframe.read_csv jest jednowątkowa, więc możesz wykonywać te sztuczki, aby pandy były znacznie szybsze, uruchamiając map () do równoległego wykonywania. Możesz użyć htop, aby zobaczyć, że przy zwykłych starych sekwencyjnych pandach dataframe.read_csv, 100% procesora na tylko jednym rdzeniu stanowi rzeczywiste wąskie gardło w pd.read_csv, a nie dysk.
Powinienem dodać, że używam dysku SSD na szybkiej magistrali karty graficznej, a nie obracającego się HD na szynie SATA6 oraz 16 rdzeni procesora.
Ponadto inną techniką, którą odkryłem, że działa świetnie w niektórych aplikacjach, jest równoległe odczytywanie pliku CSV w jednym gigantycznym pliku, rozpoczynanie każdego procesu roboczego z innym przesunięciem do pliku, zamiast wstępnego dzielenia jednego dużego pliku na wiele plików części. Użyj metody seek () i tell () pythona w każdym równoległym procesie roboczym, aby odczytać duży plik tekstowy w paskach, w różnych bajtach z przesunięciem bajtów w lokalizacjach początkowych i końcowych bajtów jednocześnie, jednocześnie. Możesz zrobić wyrażenie regularne na bajtach i zwrócić liczbę kanałów. To jest suma częściowa. Na koniec zsumuj sumy częściowe, aby uzyskać sumę globalną, gdy funkcja mapy powróci po zakończeniu procesu roboczego.
Oto kilka przykładowych testów porównawczych z wykorzystaniem sztuczki polegającej na przesunięciu bajtów równoległych:
Używam 2 plików: HIGGS.csv ma 8 GB. Pochodzi z repozytorium uczenia maszynowego UCI. all_bin .csv ma 40,4 GB i pochodzi z mojego obecnego projektu. Używam 2 programów: programu wc GNU, który jest dostarczany z Linuksem, i opracowanego przeze mnie czystego programu pytread fastread.py.
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
To około 4,5 GB / s, czyli 45 Gb / s, prędkość usuwania plików. To nie jest wirujący dysk twardy, przyjacielu. To właściwie dysk SSD Samsung Pro 950.
Poniżej znajduje się test porównawczy prędkości dla tego samego pliku liczonego w linii przez gnu wc, program skompilowany w czystym C.
Fajne jest to, że w tym przypadku mój czysty program w Pythonie zasadniczo pasował do prędkości skompilowanego programu C w GNU WC. Python jest interpretowany, ale C jest kompilowany, więc jest to dość interesujący wyczyn prędkości, myślę, że się zgodzisz. Oczywiście wc naprawdę trzeba zmienić na program równoległy, a wtedy naprawdę pobiłbym skarpety mojego programu python. Ale w obecnej formie gnu wc to tylko program sekwencyjny. Robisz, co możesz, a Python może dziś robić równolegle. Kompilacja Cython może mi pomóc (przez jakiś czas). Również pliki mapowane w pamięci nie zostały jeszcze zbadane.
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
Wniosek: Szybkość jest dobra dla czystego programu python w porównaniu do programu C. Jednak nie wystarczy używać czystego programu pythonowego nad programem C, przynajmniej do celów zliczania linii. Ogólnie technika ta może być używana do innego przetwarzania plików, więc ten kod Pythona jest nadal dobry.
Pytanie: Czy skompilowanie wyrażenia regularnego tylko raz i przekazanie go wszystkim pracownikom poprawi szybkość? Odpowiedź: Kompilacja wstępna Regex NIE pomaga w tej aplikacji. Podejrzewam, że powodem jest to, że dominują koszty związane z serializacją i tworzeniem procesów dla wszystkich pracowników.
Jeszcze jedna rzecz. Czy równoległe czytanie plików CSV w ogóle pomaga? Czy dysk jest wąskim gardłem, czy jest to procesor? Wiele tak zwanych najwyżej ocenianych odpowiedzi na stackoverflow zawiera powszechną mądrość programistyczną, że do odczytania pliku potrzebny jest tylko jeden wątek, najlepiej, jak można, mówią. Czy są jednak pewni?
Dowiedzmy Się:
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
O tak, tak. Równoległe czytanie plików działa całkiem dobrze. Cóż, proszę bardzo!
Ps. W przypadku, gdyby niektórzy z was chcieli wiedzieć, co by było, gdyby balanceFactor wynosił 2 podczas korzystania z jednego procesu roboczego? Cóż, to okropne:
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
Kluczowe części programu python fastread.py:
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
Def dla PartitionDataToWorkers to zwykły sekwencyjny kod. Zostawiłem to na wypadek, gdyby ktoś chciał poćwiczyć na programowaniu równoległym. Oddałem za darmo trudniejsze części: przetestowany i działający kod równoległy, dla twojej korzyści w nauce.
Dzięki: Projekt H2O typu open source autorstwa Arno i Cliffa oraz pracowników H2O za ich wspaniałe oprogramowanie i filmy instruktażowe, które dały mi inspirację do tego czysto pythonowego, równoległego czytnika offsetów bajtowych, jak pokazano powyżej. H2O dokonuje równoległego odczytu plików przy użyciu java, jest wywoływalny przez programy Python i R, i jest szalony szybki, szybszy niż cokolwiek na świecie przy czytaniu dużych plików CSV.