ont joué avec la fonction multicore
de Python depuis plus d'une heure maintenant, essayant de paralléliser une fonction de traversée de graphe plutôt complexe en utilisant Process
et Manager
:
import networkx as nx
import csv
import time
from operator import itemgetter
import os
import multiprocessing as mp
cutoff = 1
exclusionlist = ["cpd:C00024"]
DG = nx.read_gml("KeggComplete.gml", relabel = True)
for exclusion in exclusionlist:
DG.remove_node(exclusion)
#checks if 'memorizedPaths exists, and if not, creates it
fn = os.path.join(os.path.dirname(__file__), 'memorizedPaths' + str(cutoff+1))
if not os.path.exists(fn):
os.makedirs(fn)
manager = mp.Manager()
memorizedPaths = manager.dict()
filepaths = manager.dict()
degreelist = sorted(DG.degree_iter(),key=itemgetter(1),reverse=True)
def _all_simple_paths_graph(item, DG, cutoff, memorizedPaths, filepaths):
source = item[0]
uniqueTreePaths = []
if cutoff < 1:
return
visited = [source]
stack = [iter(DG[source])]
while stack:
children = stack[-1]
child = next(children, None)
if child is None:
stack.pop()
visited.pop()
Elif child in memorizedPaths:
for path in memorizedPaths[child]:
newPath = (Tuple(visited) + Tuple(path))
if (len(newPath) <= cutoff) and (len(set(visited) & set(path)) == 0):
uniqueTreePaths.append(newPath)
continue
Elif len(visited) < cutoff:
if child not in visited:
visited.append(child)
stack.append(iter(DG[child]))
if visited not in uniqueTreePaths:
uniqueTreePaths.append(Tuple(visited))
else: #len(visited) == cutoff:
if (visited not in uniqueTreePaths) and (child not in visited):
uniqueTreePaths.append(Tuple(visited + [child]))
stack.pop()
visited.pop()
#writes the absolute path of the node path file into the hash table
filepaths[source] = str(fn) + "/" + str(source) +"path.txt"
with open (filepaths[source], "wb") as csvfile2:
writer = csv.writer(csvfile2, delimiter=' ', quotechar='|')
for path in uniqueTreePaths:
writer.writerow(path)
memorizedPaths[source] = uniqueTreePaths
############################################################################
start = time.clock()
if __name__ == '__main__':
for item in degreelist:
test = mp.Process(target=_all_simple_paths_graph, args=(DG, cutoff, item, memorizedPaths, filepaths))
test.start()
test.join()
end = time.clock()
print (end-start)
Actuellement - bien que la chance et la magie - cela fonctionne (en quelque sorte). Mon problème est que j'utilise seulement 12 de mes 24 cœurs.
Quelqu'un peut-il expliquer pourquoi cela pourrait être le cas? Peut-être que mon code n'est pas la meilleure solution de multitraitement, ou est-ce une caractéristique de mon architecture [CPU Intel Xeon E5-2640 @ 2.50GHz x18 fonctionnant sur Ubuntu 13.04 x64]?
MODIFIER:
J'ai réussi à obtenir:
p = mp.Pool()
for item in degreelist:
p.apply_async(_all_simple_paths_graph, args=(DG, cutoff, item, memorizedPaths, filepaths))
p.close()
p.join()
Cependant, cela fonctionne TRÈS LENT! Je suppose donc que j'utilise la mauvaise fonction pour le travail. j'espère que cela aide à clarifier exactement ce que j'essaie d'accomplir!
EDIT2: .map
tentative:
partialfunc = partial(_all_simple_paths_graph, DG=DG, cutoff=cutoff, memorizedPaths=memorizedPaths, filepaths=filepaths)
p = mp.Pool()
for item in processList:
processVar = p.map(partialfunc, xrange(len(processList)))
p.close()
p.join()
Fonctionne, est plus lent que le single-core. Il est temps d'optimiser!
Trop de piles ici pour être traitées dans les commentaires, donc, où mp
est multiprocessing
:
mp.cpu_count()
devrait retourner le nombre de processeurs. Mais testez-le. Certaines plateformes sont géniales, et ces informations ne sont pas toujours faciles à obtenir. Python fait de son mieux.
Si vous démarrez 24 processus, ils feront exactement ce que vous leur demandez de faire ;-) On dirait que mp.Pool()
serait le plus pratique pour vous. Vous transmettez le nombre de processus que vous souhaitez créer à son constructeur. mp.Pool(processes=None)
utilisera mp.cpu_count()
pour le nombre de processeurs.
Ensuite, vous pouvez utiliser, par exemple, .imap_unordered(...)
sur votre instance Pool
pour répartir votre degreelist
entre les processus. Ou peut-être qu'une autre méthode Pool
fonctionnerait mieux pour vous - expérimentez.
Si vous ne pouvez pas bash le problème dans la vue du monde de Pool
, vous pouvez à la place créer un mp.Queue
Pour créer une file d'attente de travail, .put()
'noeuds (ou tranches) de nœuds, pour réduire la surcharge) sur laquelle travailler dans le programme principal, et écrivez les ouvriers dans les éléments de travail .get()
hors de cette file d'attente. Demandez si vous avez besoin d'exemples. Notez que vous devez mettre des valeurs sentinelles (une par processus) dans la file d'attente, après tous les "vrais" éléments de travail, afin que les processus de travail puissent tester la sentinelle pour savoir quand ils ont terminé.
Pour info, j'aime les files d'attente car elles sont plus explicites. Beaucoup d'autres préfèrent Pool
parce qu'ils sont plus magiques ;-)
Voici un prototype exécutable pour vous. Cela montre une façon d'utiliser imap_unordered
Avec Pool
et chunksize
qui ne nécessite la modification d'aucune signature de fonction. Bien sûr, vous devrez brancher votre vrai code ;-) Notez que l'approche init_worker
Permet de passer "la plupart" des arguments une seule fois par processeur, pas une fois pour chaque élément de votre degreeslist
. La réduction de la quantité de communication inter-processus peut être cruciale pour la vitesse.
import multiprocessing as mp
def init_worker(mps, fps, cut):
global memorizedPaths, filepaths, cutoff
global DG
print "process initializing", mp.current_process()
memorizedPaths, filepaths, cutoff = mps, fps, cut
DG = 1##nx.read_gml("KeggComplete.gml", relabel = True)
def work(item):
_all_simple_paths_graph(DG, cutoff, item, memorizedPaths, filepaths)
def _all_simple_paths_graph(DG, cutoff, item, memorizedPaths, filepaths):
pass # print "doing " + str(item)
if __name__ == "__main__":
m = mp.Manager()
memorizedPaths = m.dict()
filepaths = m.dict()
cutoff = 1 ##
# use all available CPUs
p = mp.Pool(initializer=init_worker, initargs=(memorizedPaths,
filepaths,
cutoff))
degreelist = range(100000) ##
for _ in p.imap_unordered(work, degreelist, chunksize=500):
pass
p.close()
p.join()
Je conseille fortement de faire fonctionner cela exactement tel quel, donc vous pouvez voir que c'est très rapide. Ensuite, ajoutez-y un peu de temps pour voir comment cela affecte le temps. Par exemple, en ajoutant simplement
memorizedPaths[item] = item
to _all_simple_paths_graph()
le ralentit énormément. Pourquoi? Parce que le dict devient de plus en plus gros à chaque ajout, et ce dict sûr pour les processus doit être synchronisé (sous les couvertures) entre tous les processus. L'unité de synchronisation est "le dict entier" - il n'y a pas de structure interne que la machine mp puisse exploiter pour effectuer des mises à jour incrémentielles du dict partagé.
Si vous ne pouvez pas vous permettre ces dépenses, vous ne pouvez pas utiliser une Manager.dict()
pour cela. Les possibilités d'intelligence ne manquent pas ;-)