2013-01-02 4 views
41

J'ai trois grandes listes. Premier contient bitarrays (module bitarray 0.8.0) et les deux autres contiennent des tableaux d'entiers.Mémoire partagée en multitraitement

l1=[bitarray 1, bitarray 2, ... ,bitarray n] 
l2=[array 1, array 2, ... , array n] 
l3=[array 1, array 2, ... , array n] 

Ces structures de données prennent un peu de RAM (~ 16Go au total).

Si je commence à 12 sous-processus en utilisant:

multiprocessing.Process(target=someFunction, args=(l1,l2,l3)) 

Est-ce que cela signifie que l1, l2 et l3 seront copiés pour chaque sous-processus ou seront les sous-processus partager ces listes? Ou pour être plus direct, vais-je utiliser 16 Go ou 192 Go de RAM? SomeFunction lira certaines valeurs de ces listes et effectuera ensuite certains calculs en fonction des valeurs lues. Les résultats seront renvoyés au processus parent. Les listes l1, l2 et l3 ne seront pas modifiées par someFunction.

Par conséquent, je suppose que les sous-processus n'ont pas besoin et ne copieraient pas ces énormes listes mais les partageraient plutôt avec le parent. Ce qui signifie que le programme prendrait 16 Go de RAM (quel que soit le nombre de sous-processus je commence) en raison de l'approche de copie sur écriture sous linux? Ai-je raison ou est-ce qu'il me manque quelque chose qui pourrait entraîner la copie des listes?

EDIT: Je suis toujours confus, après avoir lu un peu plus sur le sujet. D'une part, Linux utilise la copie sur écriture, ce qui signifie qu'aucune donnée n'est copiée. D'autre part, l'accès à l'objet changera son ref-count (je ne sais toujours pas pourquoi et qu'est-ce que cela signifie). Même ainsi, l'objet entier sera-t-il copié?

Par exemple, si je définis someFunction comme suit:

def someFunction(list1, list2, list3): 
    i=random.randint(0,99999) 
    print list1[i], list2[i], list3[i] 

Est-ce que vous utilisez cette fonction signifie que L1, L2 et L3 sera entièrement copié pour chaque sous-processus?

Existe-t-il un moyen de vérifier cela? Après avoir lu un peu plus et surveillé l'utilisation totale de la mémoire du système pendant l'exécution des sous-processus, il semble que des objets entiers sont effectivement copiés pour chaque sous-processus. Et il semble que ce soit parce que la référence compte.

Le comptage de référence pour l1, l2 et l3 est réellement inutile dans mon programme. C'est parce que l1, l2 et l3 seront gardés en mémoire (inchangé) jusqu'à la sortie du processus parent. Il n'est pas nécessaire de libérer la mémoire utilisée par ces listes jusque-là. En fait, je sais avec certitude que le nombre de références restera supérieur à 0 (pour ces listes et tous les objets de ces listes) jusqu'à la sortie du programme. Maintenant, la question devient: comment puis-je m'assurer que les objets ne seront pas copiés dans chaque sous-processus? Puis-je désactiver le comptage des références pour ces listes et chaque objet de ces listes?

EDIT3 Juste une note supplémentaire. Les sous-processus n'ont pas besoin de modifier l1, l2 et l3 ou tous les objets de ces listes. Les sous-processus doivent uniquement pouvoir référencer certains de ces objets sans que la mémoire soit copiée pour chaque sous-processus.

+0

http://stackoverflow.com/questions/10721915/shared-memory-objects-in-python -multiprocessing Question similaire et votre réponse. – sean

+0

Reaad par le biais et toujours pas sûr de la réponse. L'objet entier sera-t-il copié? Seulement une partie de l'objet? Une seule page contenant le refcount? Comment puis-je vérifier? – FableBlaze

+0

En raison de copier-sur-écriture, je pense que vous ne devriez pas avoir à faire quelque chose de spécial. Pourquoi ne pas l'essayer? – NPE

Répondre

31

De manière générale, il existe deux façons de partager les mêmes données:

  • multithreading
  • Mémoire partagée

de multithreading Python ne convient pas pour les tâches CPU lié (en raison de la GIL), donc la solution habituelle dans ce cas est de continuer multiprocessing. Cependant, avec cette solution, vous devez partager explicitement les données, en utilisant multiprocessing.Value et multiprocessing.Array. Notez que le partage de données entre les processus peut ne pas être le meilleur choix, en raison de tous les problèmes de synchronisation; une approche impliquant des acteurs échangeant des messages est généralement considérée comme un meilleur choix. Voir aussi Python documentation:

Comme mentionné ci-dessus, lorsque vous faites la programmation concurrente, il est généralement mieux éviter d'utiliser l'état partagé dans la mesure du possible. C'est particulièrement vrai lors de l'utilisation de plusieurs processus.

Cependant, si vous avez vraiment besoin d'utiliser des données partagées, alors le multi-traitement vous offre plusieurs moyens de le faire.

Dans votre cas, vous devez envelopper l1, l2 et l3 d'une manière compréhensible par multiprocessing (par exemple en utilisant un multiprocessing.Array), puis les passer en tant que paramètres. Notez aussi que, comme vous l'avez dit, vous n'avez pas besoin d'un accès en écriture, alors vous devez passer lock=False lors de la création des objets, sinon tous les accès seront encore sérialisés.

+0

Puis-je utiliser 'multiprocessing.Array' pour emballer des listes d'objets arbitraires tels que' bitarray() '? – FableBlaze

+0

@ anti666: Je pense que vous devriez utiliser 'multiprocessing.sharedctypes' - voir http://docs.python.org/2/library/multiprocessing.html#module-multiprocessing.sharedctypes –

+1

Alternativement, si bitarray supporte le buffer de protocole, vous pourrait le partager comme un bytearray, puis le convertir en un bitarray dans les processus engendrés. –

9

Si vous souhaitez utiliser la fonctionnalité de copie à l'écriture et que vos données sont statiques (inchangées dans les processus enfants), vous devez faire en sorte que python ne joue pas avec des blocs de mémoire contenant vos données. Vous pouvez facilement le faire en utilisant des structures C ou C++ (stl par exemple) comme conteneurs et fournir vos propres wrappers python qui utiliseront des pointeurs vers la mémoire de données (ou éventuellement copier mem de données) quand un objet de niveau python sera créé . Tout cela peut être fait très facilement avec une simplicité presque python et une syntaxe avec cython.

 
# pseudo cython 
cdef class FooContainer: 
    cdef char * data 
    def __cinit__(self, char * foo_value): 
     self.data = malloc(1024, sizeof(char)) 
     memcpy(self.data, foo_value, min(1024, len(foo_value))) 

    def get(self): 
     return self.data 

 
# python part 
from foo import FooContainer 

f = FooContainer("hello world") 
pid = fork() 
if not pid: 
    f.get() # this call will read same memory page to where 
      # parent process wrote 1024 chars of self.data 
      # and cython will automatically create a new python string 
      # object from it and return to caller 

Le pseudo-code ci-dessus est mal écrit. Ne l'utilisez pas. Au lieu de self.data devrait être conteneur C ou C++ dans votre cas.

0

Vous pouvez utiliser memcached ou Redis et définir chacun comme une valeur clé paire { 'l1' ...