Qualche contesto in anticipo su da dove vengo. Gli snippet di codice sono alla fine.
Quando posso, preferisco usare uno strumento open source come H2O per fare letture di file CSV parallele ad altissime prestazioni, ma questo strumento è limitato nel set di funzionalità. Finisco per scrivere un sacco di codice per creare pipeline di data science prima di alimentare il cluster H2O per l'apprendimento supervisionato.
Ho letto file come set di dati HIGGS da 8 GB dal repository UCI e persino file CSV da 40 GB per scopi di data science molto più velocemente aggiungendo un sacco di parallelismo con l'oggetto pool della libreria a elaborazione multipla e la funzione mappa. Ad esempio, il clustering con le ricerche dei vicini più vicini e anche gli algoritmi di clustering DBSCAN e Markov richiedono un po 'di finezza di programmazione parallela per aggirare alcuni problemi di memoria e tempo di clock davvero impegnativi.
Di solito mi piace spezzare il file in ordine di riga in parti usando prima gli strumenti gnu e poi glob-file maschera tutti per trovarli e leggerli in parallelo nel programma python. Uso qualcosa come 1000+ file parziali comunemente. Fare questi trucchi aiuta immensamente con velocità di elaborazione e limiti di memoria.
Panda dataframe.read_csv è a thread singolo, quindi puoi fare questi trucchi per rendere i panda abbastanza più veloci eseguendo una mappa () per l'esecuzione parallela. Puoi usare htop per vedere che con i semplici vecchi panda sequenziali dataframe.read_csv, 100% cpu su un solo core è l'effettivo collo di bottiglia in pd.read_csv, non il disco.
Dovrei aggiungere che sto usando un SSD sul bus veloce della scheda video, non un HD rotante sul bus SATA6, più 16 core della CPU.
Inoltre, un'altra tecnica che ho scoperto funziona alla grande in alcune applicazioni è la lettura parallela di file CSV all'interno di un unico file gigante, avviando ogni lavoratore con offset diverso nel file, anziché suddividere un grande file in molti file di parti. Utilizzare il file seek () e tell () di python in ogni lavoratore parallelo per leggere il file di testo grande in strip, in posizioni di byte di inizio e di byte di fine byte diverse nel file grande, contemporaneamente allo stesso tempo. È possibile eseguire una regex findall sui byte e restituire il conteggio degli avanzamenti di riga. Questa è una somma parziale. Infine sommare le somme parziali per ottenere la somma globale quando la funzione della mappa ritorna dopo che i lavoratori hanno finito.
Di seguito sono riportati alcuni esempi di benchmark che utilizzano il trucco dell'offset di byte parallelo:
Uso 2 file: HIGGS.csv ha 8 GB. Viene dal repository di apprendimento automatico dell'UCI. all_bin .csv è 40,4 GB e proviene dal mio progetto attuale. Uso 2 programmi: il programma GNU wc fornito con Linux e il programma pastron fastread.py puro che ho sviluppato.
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
Sono circa 4,5 GB / s, o 45 Gb / s, la velocità del file slurping. Non è un disco rigido rotante, amico mio. Questo è in realtà un SSD Samsung Pro 950.
Di seguito è riportato il benchmark di velocità per lo stesso file che viene contato in linea da gnu wc, un programma compilato in C puro.
Ciò che è bello è che puoi vedere che il mio programma Python puro ha sostanzialmente eguagliato la velocità del programma C compilato da gnu wc in questo caso. Python è interpretato ma C è compilato, quindi questa è una prodezza piuttosto interessante di velocità, penso che saresti d'accordo. Ovviamente, il wc ha davvero bisogno di essere cambiato in un programma parallelo, e poi avrebbe davvero battuto le calze dal mio programma Python. Ma com'è oggi, gnu wc è solo un programma sequenziale. Fai ciò che puoi e Python può fare parallelamente oggi. La compilazione di Cython potrebbe essere in grado di aiutarmi (per qualche altra volta). Anche i file mappati in memoria non sono stati ancora esplorati.
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
Conclusione: la velocità è buona per un programma Python puro rispetto a un programma C. Tuttavia, non è abbastanza buono per usare il programma Python puro sul programma C, almeno per scopi di lincounting. Generalmente la tecnica può essere utilizzata per l'elaborazione di altri file, quindi questo codice Python è ancora buono.
Domanda: Compilare il regex solo una volta e passarlo a tutti i lavoratori migliorerà la velocità? Risposta: La pre-compilazione Regex NON aiuta in questa applicazione. Suppongo che la ragione sia che il sovraccarico della serializzazione dei processi e della creazione per tutti i lavoratori sta dominando.
Un'altra cosa. La lettura parallela di file CSV aiuta anche? Il disco è il collo di bottiglia o è la CPU? Molte cosiddette risposte più votate su StackOverflow contengono la comune saggezza degli sviluppatori secondo cui è necessario un solo thread per leggere un file, meglio che puoi fare, dicono. Sono sicuri, però?
Scopriamolo:
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 sì, sì lo fa. La lettura di file paralleli funziona abbastanza bene. Bene ecco qua!
Ps. Nel caso in cui qualcuno di voi volesse sapere, cosa succede se balanceFactor era 2 quando si utilizza un processo a singolo lavoratore? Bene, è orribile:
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
Parti chiave del programma pastron 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
La definizione di PartitionDataToWorkers è solo un normale codice sequenziale. L'ho lasciato fuori nel caso in cui qualcun altro voglia fare pratica su come sia la programmazione parallela. Ho regalato gratuitamente le parti più dure: il codice parallelo testato e funzionante, per il tuo vantaggio di apprendimento.
Grazie a: Il progetto H2O open-source, di Arno e Cliff e dello staff di H2O per i loro fantastici software e video didattici, mi ha fornito l'ispirazione per questo lettore di offset a byte parallelo ad alte prestazioni in puro pitone, come mostrato sopra. H2O esegue la lettura di file paralleli utilizzando Java, è richiamabile da programmi Python e R ed è pazzo veloce, più veloce di qualsiasi altra cosa al mondo a leggere file CSV di grandi dimensioni.