Python: multiprocessing vs threading
Par Narann le mercredi, 4 avril 2012, 22:52 - Script et code - Lien permanent
Ce billet va tenter de montrer, principalement par l'exemple les différences entre le module threading et le module... multiprocessing! Je vous propose donc deux petits codes pour démarrer rapidement.
La plupart des TD Maya on déjà entendu parler du threading. Non pas pour savoir a quoi ça servait exactement mais parce que Maya a toujours eu du mal avec ce machin la.
Cela dit, ce billet est très général et n'aborde pas les problèmes de threading dans Maya. Juste les différences entre le module threading et multiprocessing.
Le module threading
Pour faire simple, avec ce module vous "fabriquez" des objets de type Thread, chacun ayant une tache a accomplir. Vous lancez ces objets dans la nature et vous attendez qu'ils reviennent...
Comment ça se passe dans la pratique?
Déjà, il vous faut une fonction suffisamment lourde pour mettre à genoux votre machine:
K = 50 def CostlyFunction(z): result = 0 for k in xrange(1, K+2): result += z ** (1 / k**1.5) return result
Honteusement pompée du fil Stack Overflow: multiprocess or threading in python?.
Comme je vous disais, il vous faut un objet de type Thread:
import threading K = 50 class CostlyThread(threading.Thread): def __init__(self, value): threading.Thread.__init__(self) self.value = value def run(self): # notre fonction result = 0 z = self.value for k in xrange(1, K+2): result += z ** (1 / k**1.5) self.value = result
Et on balance tout!
import time # timer startTime = time.time() # va stocker les threads qu'on va lancer. threadList = [] for i in xrange(100000) : # on lance cent milles threads curThread = CostlyThread(i) curThread.start() # on lance le thread threadList.append(curThread) # on ajoute le thread a la list # maintenant qu'on a tout lance, on récupère tout! resultList = [] for curThread in threadList : curThread.join() # on attend que le thread ait fini result = curThread.value # on récupère sa valeur resultList.append(result) print time.time() - startTime
Sauvez tout ça dans un fichier python puis lancez un moniteur système et regardez bien ce que Python va faire...
21.4 secondes plus tard... :siffle:
Vous remarquerez que Python n'a pas tous les processeurs à fond. :redface:
En fait, le threading n'a pas pour vocation de faire "beaucoup de petits calculs". Il n'est pas efficace pour ce genre de chose (je ne rentre pas dans les détails, c'est très bien expliqué dans le fil Stack Overflow: Are Python threads buggy?).
Un des principal intérêt du threading c'est sa mise en place. Il est très simple à utiliser. On va principalement s'en servir pour faire des opérations nécessitant de la "latence".
Je pense qu'on doit pouvoir aller plus loin mais personnellement, je l'utilise souvent quand je dois faire plusieurs appels réseaux dans une boucle (récupérer la date de modification d'une liste de fichiers) c'est beaucoup plus efficace que d'attendre a chaque fois que le réseau nous donne la main pour redemander une informations similaire juste après.
Pas convaincu?
25709 existences de fichier vérifiées en 38.35 secondes :IFuckTheWorld:
J'ose même pas imaginer le temps que ça aurait pris si j'avais fait la même chose en récursif...
Après, le réseau en question était solide (c'est pas du routeur de promotion Carrefour! :baffed: ). Cela dit, suivant l’opération faite par le thread vous pouvez plomber le réseau (ce sera toujours moins pire que Nuke et After Effect en rendu cela dit :trollface: )... Donc faite attention.
Viens maintenant le problème de "calcul brut". Si le threading ne permet pas d'utiliser l’intégralité de son processeur, comment puis-je faire des calculs très lourds?
C'est la seconde partie de ce billet! :hehe:
Le module multiprocessing
Ce module est un petit peu plus subtil à utiliser et il y a plusieurs façons de faire.
La méthode que nous allons voir est l'une des (la?) plus efficace:
import multiprocessing as mp # timer startTime = time.time() pool = mp.Pool() asyncResult = pool.map_async(CostlyFunction, xrange(100000)) # on lance aussi cent milles operations resultList = asyncResult.get() # asyncResult.get() is a list of values print time.time() - startTime
0.9 secondes plus tard (Et ouai, faut suivre...)
Vous n'avez peut être rien vu mais si vous avez toujours votre moniteur système d'ouvert avec un graph de charge processeur, vous verrez que pendant ce court laps de temps, tous les processeurs étaient en pleine activité...
Ajoutez un zéro et regardez comment votre processeur il pleure sa maman (on gaspille aussi plein d’énergie hein! :seSentCon: ).
Je m’arrête la. Le module multiprocessing offre beaucoup plus de possibilités (gestion d'une queue). Mais pour l'instant, je n'ai jamais eu a l'utiliser. :pasClasse:
Conclusion
J’espère que ce petit billet vous aura donné des bouts de code suffisant pour débuter. Personnellement, j’apprécie beaucoup la simplicité du module threading qu'on peut sortir dans pas mal de situations des lors qu'on touche au fichiers.
A bientôt!
Dorian
Commentaires
Ca cest bien
Très intéressant et surtout bien expliqué - avec une bonne orthographe. Ce qui me réjouit profondément.
En effet, étant littéraire de formation, je n'ai toujours pas compris pourquoi/comment on peut s'imaginer programmer correctement à travers un langage technique sans être capable d'utiliser les finesses du langage naturel.
Merci et bonne continuation.
ALB
Merci, c'est rare que l'on me félicite sur mon orthographe.
Jt' interdis de dire ca
Merci
!
C'est carrément super puissant ton truc, je sens que je vais en user et en abuser ;).
très bien, mais pourrais tu m´expliquer comment cela marche dans maya.
J´ai un script qui est long a s´executer et j´aimerai qu´il soit multiproc.
Merci
Maya ne peut pas exécuter ces commandes en multithreading. On ne peut pas, par exemple, faire un getAttr/setAttr en multithread.
Idem pour Python qui ne supporte pas nativement le multithreading sur plusieurs core (le module multiprocessing étant celons moi un gros hack).
Donc multithreader un script, tu oublie. ^^
Je te conseil la lecture de la page Python and Threading qui est très intéressante.
Dorian
Cool l'explication !!!
En revanche, juste pour être sure que j'ai bien compris, on est d'accord que dans le cas présent, lancer la fonction de manière classique va bien plus vite qu'en multithread ?
Vue que pour ce genre de fonction, il n'y a probablement rien de bloquant et qu'en plus il y a une grosse perte de temps avec la manipulation des 2 listes ? (ou alors j'ai loupé une truc)
La fonction "CostlyFunction()" est juste une fonction qui "fait des calculs lourds". Les lancer via le module multiprocessing est plus efficace que via le module threading.
La fonction lancé 10000 en threading a pris 21.4 secondes et en multiprocessing elle a pris 0.9 secondes (il faut lire. ^^' ).
Le cout de manipulation des deux listes est quasiment nul. Dans la première partie on génère les thread, on les lances et on les stock dans une liste. Puis on les récupères les uns après les autres. C'est souvent comme ça qu'on fait quand on manipule des threads.
En multiprocessing c'est un peu plus technique. On a moins de polyvalence.
J'ai eu du mal a comprendre ce que tu voulais dire en fait.