Un peu de contexte pour savoir d'où je viens. Les extraits de code sont à la fin.
Lorsque je le peux, je préfère utiliser un outil open source comme H2O pour effectuer des lectures de fichiers CSV parallèles de très haute performance, mais cet outil est limité dans l'ensemble de fonctionnalités. Je finis par écrire beaucoup de code pour créer des pipelines de science des données avant d'alimenter le cluster H2O pour l'apprentissage supervisé proprement dit.
J'ai lu des fichiers comme un ensemble de données HIGGS de 8 Go à partir du référentiel UCI et même des fichiers CSV de 40 Go à des fins de science des données beaucoup plus rapidement en ajoutant beaucoup de parallélisme avec l'objet pool et la fonction de carte de la bibliothèque de multitraitement. Par exemple, le clustering avec les recherches de voisins les plus proches et les algorithmes de clustering DBSCAN et Markov nécessitent une finesse de programmation parallèle pour contourner certains problèmes de mémoire et d'horloge murale très difficiles.
J'aime généralement diviser le fichier en plusieurs parties en utilisant d'abord les outils gnu, puis les masquer tous pour les trouver et les lire en parallèle dans le programme python. J'utilise couramment quelque chose comme plus de 1000 fichiers partiels. Faire ces astuces aide énormément avec la vitesse de traitement et les limites de mémoire.
Le fichier pandas dataframe.read_csv est un thread unique, vous pouvez donc faire ces astuces pour accélérer les pandas en exécutant une carte () pour une exécution parallèle. Vous pouvez utiliser htop pour voir qu'avec de vieux pandas séquentiels dataframe.read_csv, 100% de processeur sur un seul cœur est le véritable goulot d'étranglement dans pd.read_csv, pas le disque du tout.
Je dois ajouter que j'utilise un SSD sur un bus de carte vidéo rapide, pas un HD tournant sur le bus SATA6, plus 16 cœurs de processeur.
En outre, une autre technique que j'ai découverte fonctionne très bien dans certaines applications: le fichier CSV parallèle lit tout dans un fichier géant, en démarrant chaque travailleur à un décalage différent dans le fichier, plutôt que de pré-fractionner un gros fichier en plusieurs fichiers de pièce. Utilisez la recherche de fichier () et tell () de python dans chaque travailleur parallèle pour lire le gros fichier texte en bandes, à différents emplacements de début et de fin de décalage d'octet dans le gros fichier, le tout en même temps. Vous pouvez effectuer une recherche regex sur les octets et renvoyer le nombre de sauts de ligne. Il s'agit d'une somme partielle. Enfin, additionnez les sommes partielles pour obtenir la somme globale lorsque la fonction de carte revient une fois que les travailleurs ont terminé.
Voici quelques exemples de références utilisant l'astuce de décalage d'octets parallèles:
J'utilise 2 fichiers: HIGGS.csv fait 8 Go. Il provient du référentiel d'apprentissage automatique UCI. all_bin .csv fait 40,4 Go et provient de mon projet actuel. J'utilise 2 programmes: le programme GNU wc fourni avec Linux et le programme pur python fastread.py que j'ai développé.
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
C'est quelque 4,5 Go / s, ou 45 Go / s, la vitesse de slurping des fichiers. Ce n'est pas un disque dur qui tourne, mon ami. C'est en fait un SSD Samsung Pro 950.
Ci-dessous se trouve la référence de vitesse pour le même fichier compté par gnu wc, un programme compilé en C pur.
Ce qui est cool, c'est que vous pouvez voir que mon programme en python pur correspondait essentiellement à la vitesse du programme C compilé par gnu wc dans ce cas. Python est interprété mais C est compilé, c'est donc un exploit de vitesse assez intéressant, je pense que vous serez d'accord. Bien sûr, wc doit vraiment être changé en un programme parallèle, et alors il devrait vraiment battre les chaussettes de mon programme python. Mais tel qu'il est aujourd'hui, gnu wc n'est qu'un programme séquentiel. Vous faites ce que vous pouvez, et python peut faire du parallèle aujourd'hui. La compilation de Cython pourrait m'aider (pour une autre fois). Les fichiers mappés en mémoire n'ont pas encore été explorés.
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
Conclusion: La vitesse est bonne pour un programme en python pur par rapport à un programme C. Cependant, il n'est pas suffisant d'utiliser le programme python pur sur le programme C, au moins à des fins de comptage de lignes. Généralement, la technique peut être utilisée pour d'autres traitements de fichiers, donc ce code python est toujours bon.
Question: Est-ce que la compilation de l'expression régulière une seule fois et sa transmission à tous les employés améliorera la vitesse? Réponse: La pré-compilation Regex n'aide PAS dans cette application. Je suppose que la raison en est que les frais généraux de sérialisation et de création de processus pour tous les travailleurs sont dominants.
Encore une chose. La lecture de fichiers CSV parallèle aide-t-elle même? Le disque est-il le goulot d'étranglement ou est-ce le CPU? De nombreuses réponses dites les mieux notées sur stackoverflow contiennent la sagesse courante des développeurs selon laquelle vous n'avez besoin que d'un seul thread pour lire un fichier, le mieux que vous puissiez faire, disent-ils. Mais sont-ils sûrs?
Découvrons-le:
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 oui, oui. La lecture de fichiers en parallèle fonctionne plutôt bien. Et bien voilà!
Ps. Dans le cas où certains d'entre vous voudraient savoir, que se passe-t-il si le balanceFactor était de 2 lors de l'utilisation d'un seul processus de travail? Eh bien, c'est horrible:
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
Parties clés du programme 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
Le def pour PartitionDataToWorkers est juste du code séquentiel ordinaire. Je l'ai laissé de côté au cas où quelqu'un d'autre voudrait se familiariser avec la programmation parallèle. J'ai donné gratuitement les parties les plus difficiles: le code parallèle testé et fonctionnel, pour votre bénéfice d'apprentissage.
Merci à: Le projet open source H2O, par Arno et Cliff et le personnel H2O pour leurs excellents logiciels et vidéos pédagogiques, qui m'ont inspiré ce lecteur de décalage d'octets parallèle en python pur de haute performance comme indiqué ci-dessus. H2O fait la lecture de fichiers en parallèle en utilisant java, est appelable par les programmes python et R, et est incroyablement rapide, plus rapide que n'importe quoi sur la planète pour lire de gros fichiers CSV.