web-dev-qa-db-fra.com

Comment écrire dans une variable partagée en python joblib

Le code suivant met en parallèle une boucle for.

import networkx as nx;
import numpy as np;
from joblib import Parallel, delayed;
import multiprocessing;

def core_func(repeat_index, G, numpy_arrary_2D):
  for u in G.nodes():
    numpy_arrary_2D[repeat_index][u] = 2;
  return;

if __== "__main__":
  G = nx.erdos_renyi_graph(100000,0.99);
  nRepeat = 5000;
  numpy_array = np.zeros([nRepeat,G.number_of_nodes()]);
  Parallel(n_jobs=4)(delayed(core_func)(repeat_index, G, numpy_array) for repeat_index in range(nRepeat));
  print(np.mean(numpy_array));

Comme on peut le constater, la valeur attendue à imprimer est 2. Cependant, lorsque je lance mon code sur un cluster (multicœur, mémoire partagée), il renvoie 0.0. 

Je pense que le problème est que chaque opérateur crée sa propre copie de l'objet numpy_array et que celle créée dans la fonction principale n'est pas mise à jour. Comment puis-je modifier le code de sorte que le numpy array numpy_array puisse être mis à jour?

15
user3813057

joblib utilise le pool de multitraitement de processus par défaut, comme son manuel dit:

Sous le capot, l'objet Parallel crée un pool de multitraitement qui forque l'interpréteur Python dans plusieurs processus pour exécuter chacun des fichiers les éléments de la liste. La fonction différée est une astuce simple pour être capable de créer un tuple (fonction, arguments, kwargs) avec un appel de fonction syntaxe.

Ce qui signifie que chaque processus hérite de l'état d'origine du tableau, mais tout ce qu'il écrit dedans est perdu lorsque le processus se termine. Seul le résultat de la fonction est renvoyé au processus appelant (principal). Mais vous ne renvoyez rien, donc None est renvoyé.

Pour rendre le tableau partagé modifiable, vous avez deux moyens: utiliser des threads et utiliser la mémoire partagée.


Les threads, contrairement aux processus, partagent la mémoire. Ainsi, vous pouvez écrire sur le tableau et chaque travail verra ce changement. Selon le manuel joblib, cela se fait de la manière suivante:

  Parallel(n_jobs=4, backend="threading")(delayed(core_func)(repeat_index, G, numpy_array) for repeat_index in range(nRepeat));

Quand vous le lancez:

$ python r1.py 
2.0

Cependant, lorsque vous écrivez des choses complexes dans le tableau, assurez-vous de gérer correctement les verrous autour des données ou des morceaux de données, sinon vous atteindrez les conditions de concurrence (google it).

Lisez aussi attentivement GIL, car le multithreading informatique en Python est limité (contrairement au multithreading I/O).


Si vous avez toujours besoin des processus (à cause de GIL, par exemple), vous pouvez placer ce tableau dans la mémoire partagée.

Ceci est un sujet un peu plus compliqué, mais joblib + numpy exemple de mémoire partagée est également présenté dans le manuel joblib.

5
Sergey Vasilyev

Comme Sergey l'a écrit dans sa réponse, les processus ne partagent pas l'état et la mémoire. C'est pourquoi vous ne voyez pas la réponse attendue.

Threads partagent l'état et l'espace mémoire, car ils s'exécutent sous le même processus. Ceci est utile si vous avez plusieurs opérations d'E/S. Cela ne vous apportera pas plus de puissance de calcul (plus de processeurs) à cause du GIL

Une technique permettant de communiquer entre processus est la méthode Objets proxy utilisant Manager. Vous créez un objet de gestionnaire, qui synchronise les ressources entre les processus. 

Un objet manager renvoyé par Manager () contrôle un processus serveur contenant les objets Python et permettant à d'autres processus de les manipuler à l'aide de proxies.

Je n'ai pas testé ce code (je n'ai pas tous les modules que vous utilisez) et cela pourrait nécessiter davantage de modifications du code, mais l'utilisation de l'objet Manager devrait ressembler à ceci

if __== "__main__":
    G = nx.erdos_renyi_graph(100000,0.99);
    nRepeat = 5000;

    manager = multiprocessing.Manager()
    numpys = manager.list(np.zeros([nRepeat, G.number_of_nodes()])

    Parallel(n_jobs=4)(delayed(core_func)(repeat_index, G, numpys, que) for repeat_index in range(nRepeat));
    print(np.mean(numpys));
0
Chen A.