web-dev-qa-db-fra.com

Mémoire partagée en multitraitement

J'ai trois grandes listes. Premièrement contient bitarrays (module bitarray 0.8.0) et les deux autres contiennent des tableaux d'entiers.

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 (~ 16 Go au total).

Si je démarre 12 sous-processus en utilisant:

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

Cela signifie-t-il que l1, l2 et l3 seront copiés pour chaque sous-processus ou les sous-processus partageront-ils ces listes? Ou, pour être plus direct, utiliserai-je 16 Go ou 192 Go de RAM?

someFunction lira des valeurs à partir de ces listes, puis effectuera des 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 une fonction.

Par conséquent, je suppose que les sous-processus n'ont pas besoin et ne copieraient pas ces listes énormes mais les partageraient simplement avec le parent. Cela signifie que le programme prendrait 16 Go de RAM (quel que soit le nombre de sous-processus que je lance) en raison de l'approche de copie sur écriture sous Linux. Suis-je correct ou manque-t-il quelque chose qui provoquerait 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 nombre de références (je ne sais toujours pas pourquoi et ce que cela signifie). Malgré tout, l'objet entier sera-t-il copié?

Par exemple si je définis une fonction comme suit:

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

Est-ce que l'utilisation de cette fonction signifierait que l1, l2 et l3 seront entièrement copiés pour chaque sous-processus?

Y a-t-il un moyen de vérifier cela?

EDIT2 Après en 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 soient effectivement copiés pour chaque sous-processus. Et cela semble être dû au comptage des références.

Le comptage de références pour l1, l2 et l3 est en fait inutile dans mon programme. Cela est dû au fait que les niveaux l1, l2 et l3 seront conservés en mémoire (inchangés) jusqu'à la fin 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 chaque objet de ces listes) jusqu'à la fin du programme.

Alors maintenant, la question est la suivante: comment puis-je m'assurer que les objets ne seront pas copiés dans chaque sous-processus? Puis-je peut-être désactiver le comptage des références pour ces listes et chaque objet de ces listes?

EDIT Juste une note supplémentaire. Les sous-processus n'ont pas besoin de modifier l1, l2 et l3 ou des objets de ces listes. Les sous-processus doivent uniquement pouvoir référencer certains de ces objets sans provoquer la copie de la mémoire pour chaque sous-processus.

56
FableBlaze

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

  • Multithreading
  • La memoire partagée

Le multithreading de Python ne convient pas aux tâches liées à la CPU (à cause de la GIL), la solution habituelle dans ce cas est donc de passer à multiprocessing. Cependant, avec cette solution, vous devez explicitement partager les données en utilisant multiprocessing.Value et multiprocessing.Array .

Notez que le partage de données entre 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 documentation Python :

Comme mentionné ci-dessus, lors de la programmation concurrente, il est généralement préférable d'éviter autant que possible d'utiliser l'état partagé. Cela est particulièrement vrai lorsque vous utilisez plusieurs processus.

Cependant, si vous avez vraiment besoin d'utiliser des données partagées, le multitraitement vous permet de le faire de différentes manières.

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 transmettez-les en tant que paramètres.
Notez également que, comme vous avez dit que vous n’avez pas besoin d’un accès en écriture, vous devez alors passer lock=False lors de la création des objets, sinon tous les accès seront encore sérialisés.

45
rob

Si vous souhaitez utiliser la fonctionnalité de copie sur écriture et que vos données sont statiques (inchangées dans les processus enfants), vous devez créer python ne vous mêlez pas des blocs de mémoire où se trouvent vos données. Vous pouvez facilement le faire en utilisant des structures C ou C++ (stl par exemple) en tant que conteneurs et en fournissant vos propres wrappers python) qui utiliseront des pointeurs vers la mémoire de données (ou éventuellement une copie de données mem) lorsque python Un objet de niveau sera éventuellement créé.Tout cela peut être fait très facilement avec presque python simplicité et syntaxe avec cython .

 # pseudo cython 
 classe cdef 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 partie 
 de foo import FooContainer 
 
 f = FooContainer ("bonjour le monde") 
 pid = fork () 
 si ce n'est pas pid: 
 f.get () # cet appel lira la même page de mémoire où 
 # le processus parent a écrit 1024 caractères de self.data 
 # et cython créeront automatiquement un nouvel objet python string 
 # et le renverront à l'appelant 

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

11
Turnaev Evgeny

Vous pouvez utiliser memcached ou redis et les définir comme paire de valeurs de clé {'l1' ...

3
CrabbyPete