Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

Paralléliser sans sérialiser (pickle)

+4 votes

J'ai un script qui doit traiter environ 20 fichiers de 100mo.
Le traitement de chaque fichier est un peu coûteux en calcul (l'I/O se fait très vite en comparaison) donc je souhaite paralléliser. Le résultat du traitement est une liste très longue de valeurs. J'ai besoin de regrouper ces listes en une seule par la suite.

La doc sur la parallélisation et les différents articles que j'ai lu m'incitent à utiliser le module de multiprocessing https://docs.python.org/2/library/multiprocessing.html

Mon problème est le suivant, les processus de traitement s’exécutent plutôt vite, mais pour renvoyer leur résultat, ce dernier doit être transmis sérialisé via pickle (ou "dill"), vu qu'il est plutôt gros, ça consomme beaucoup de temps (10 fois plus que le traitement) et de RAM (ce qui peut provoquer des crashs en production).

Il y a t'il une alternative qui me permet de tirer parti de la parallélisation sans sérialiser ? de partager directement l'objet en mémoire d'un process ?

EDIT:
Pour donner quelques chiffres (à la louche) :

En séquentiel :

  • J'ai donc 20 fichiers de 100mo de texte. Je peux les parser de
    manière indépendante. (taille non fixe, ça peut monter à 10Go de fichiers, mais j'ai pas mal de RAM à dispo)
  • Les fichiers contiennent dans chaque ligne 3 informations qui peuvent m'intéresser, disons deux informations de clé, et plusieurs
    valeur pour ce couple de clé. Il y a pas mal de parsing.
  • Ça consomme environ 10s par fichier de manière naïve. Pour le moment je mets tout ça dans un dict qui contient 400k clés-valeurs
    par fichier.
  • Pour la suite, j'aimerais regrouper ces dictionnaires en un seul. De manière séquentielle ça coûte quasi rien.

Ça me semble du coup être un peu un problème "Map-Reduce".

Ce qui me gène avec Pickle, c'est que ça tue tout intérêt pour moi à multiprocesser. Ça introduit une étape de sérialisation (par le process fils) puis de dé-sérialisation (par le process parent) alors qu'on veut justement que chaque process dé-sérialise. On fait beaucoup plus de boulot et j'ai augmenté la charge du process principal. Accessoirement aussi ça consomme quelque chose comme 5 fois la taille des fichiers en RAM et traiter un fichier prend 10 fois plus de temps.

demandé 3-Dec-2015 par Heretron (140 points)
reclassée 4-Dec-2015 par Heretron

Pas de solution pour partager un objet python à ma connaissance (à moins de le sérialiser et de partager les données brutes). En revanche il est possible de partager d'autres choses, il faudrait savoir ce que contiennent tes listes.

Comment tu récupère tes listes actuellement ? Ton problème principal est la lenteur, ou la RAM ?

Et pour finir, un extrait de la doc :
As far as possible one should try to avoid shifting large amounts of data between processes.

Si la consommation de RAM est un problème, introduire des générateurs peut grandement aider, mais tout dépend si tes traitements sur les fichiers en question sont séquentiels ou non.

Bref, il nous faudrait un peu plus d'informations sur ton problème : quel type de traitement est appliqué sur les fichiers, et de quelle manière est composée la liste de sortie.

J'ai rajouté des chiffres à titre d'information et précisé mon problème avec Pickle.

Si je comprend bien, le traitement sur les fichiers est séquentiel (on peut traiter les lignes indépendamment les unes des autres). Du coup la question de la RAM est secondaire, puisqu'il n'est pas utile de traiter tout le fichier d'un coup.

Si tu tiens à rester en multiprocessing, éviter le pickling ne me semble pas possible. Les seuls objets que l'on peut "réellement" partager entre plusieurs processus sont indiqués dans la doc, et ce n'est pas possible pour un dictionnaire il me semble (ce sont des objets ctypes).

En revanche, changer d'approche peut éventuellement accélérer le traitement même avec du pickling (par ex. en envoyant les données par chunks plus petits ou plus grands).

Je fais mes recherches, je tombe sur ça : https://www.jeffknupp.com/blog/2013/06/30/pythons-hardest-problem-revisited/
Je sais pas si mon problème est mal posé ou si python est pas adapté. De ce que je comprends, je me bas contre python, je risque pas trop de gagner.

Très bon le lien.

Le GIL, c'est ce qui t'oblige à utiliser multiprocessing au leu de passer par des threads (auquel cas il serait infiniment plus simple de partager des objets). Et effectivement, actuellement il n'y a pas moyen de s'en débarrasser.

Donc oui, a priori, il te faut une solution de contournement.

3 Réponses

+1 vote
 
Meilleure réponse

Utilise multiprocessing, MAIS, ne passe pas le fichier complet depuis ton processus principal. Passe juste le chemin du fichier, et charge chaque fichier dans ton sous-processus. Si le resultat est petit, tu peux le retourner directement, sinon, savegarde le directement dans un autre fichier depuis ton processus.

répondu 3-Fev-2016 par Sam (4,974 points)
sélectionné 3-Fev-2016 par Heretron

Merci, au final j'ai fais comme ça. J'ai gardé les calculs indépendant le plus loin possible dans le processus. J'ai besoin de faire une "médiane" (en gros) sur tout l'ensemble des données, j'ai trouvé une ruse de sioux plutôt que de faire de la façon naturelle.

Ma question était surtout pour savoir si je me servais du multiprocessing de travers. En passant tes derniers articles qui abordent les limites de python avec le multi-core m'ont beaucoup éclairé, merci beaucoup !

+2 votes

Je te conseille également d'utiliser le multiprocessing.

Pour partager de l'information entre plusieurs processus, il est recommandé d'utiliser une file d'attente prévue à cet effet.

Tu peux aussi utiliser la programmation asynchrone, sauf si tu as une forte consommation CPU où le multiprocessing sera plus efficace.

Sur la même page de ton lien, regarde la section : 16.6.1.2. Exchanging objects between processes

Enfin tu peux peut être t'inspirer de ce que l'API API Hour réalise, qui est un mélange d'asynchrone et de multiprocessing.

https://pypi.python.org/pypi/api_hour

---mode HS---
Ensuite si vraiment tu veux vraiment un truc plus performant, il y a d'autres langages comme golang qui fait des merveilles avec les goroutines et les channels mais c'est un univers différent. Il faut seulement savoir que pour du distribué Python n'est pas forcément le plus adapté du fait de sa conception intrinsèque.

répondu 3-Dec-2015 par DoubleNain (1,642 points)

Les Queues et Pipes utilisent aussi Pickle

from multiprocessing import Process, Pipe
def f(conn):
    conn.send([42, lambda x:x, 'hello'])
    conn.close()

parent_conn, child_conn = Pipe()
p = Process(target=f, args=(child_conn,))
p.start()
print parent_conn.recv()
p.join()

(j’introduis un lambda que Pickle digère pas pour m'en assurer)

Le truc est plutôt consommateur de CPU que de IO, c'est pour ça que je voudrais utiliser plusieurs process. En l'état la communication du résultat au demandeur qui prend un temps beaucoup plus de temps que le calcul (lui même plus long que la lecture).

+2 votes

Une autre solution consiste à passer par un intermédiaire pour l’échange de données entre processus. Quand le processus a fini, il transfert la réponse dans le système intermédiaire et prévient l’autre processus (par une méthode classique) que la solution est disponible à tel emplacement.
Ce système intermédiaire peut très bien être un ensemble de fichiers, une bases de données (nosql type clé/valeur comme redis ou classique), etc..

répondu 2-Fev-2016 par etno712 (288 points)

Est ce que ces intermédiaires permettent de court-circuiter la sérialisation ? J'ai envie de répondre non pour les BDD / Fichier. Redis je sais pas trop je connais pas, un indice ?

Oui, pour une base de données relationnelles, il existe le type blob. En revanche pour redis et consort, tout dépend de ta donnée…

...