118 votes

multiprocessing : partage d'un gros objet en lecture seule entre les processus ?

Les processus enfants engendrés par multitraitement partager des objets créés plus tôt dans le programme ?

J'ai la configuration suivante :

do_some_processing(filename):
    for line in file(filename):
        if line.split(',')[0] in big_lookup_object:
            # something here

if __name__ == '__main__':
    big_lookup_object = marshal.load('file.bin')
    pool = Pool(processes=4)
    print pool.map(do_some_processing, glob.glob('*.data'))

Je charge un gros objet en mémoire, puis je crée un groupe de travailleurs qui doivent utiliser ce gros objet. Le gros objet est accessible en lecture seule, je n'ai pas besoin d'en transmettre les modifications entre les processus.

Ma question est la suivante : le grand objet est-il chargé dans la mémoire partagée, comme il le serait si je créais un processus dans unix/c, ou chaque processus charge-t-il sa propre copie du grand objet ?

Mise à jour : pour clarifier davantage - big_lookup_object est un objet de consultation partagé. Je n'ai pas besoin de le diviser et de le traiter séparément. Je dois en garder une seule copie. Le travail dont j'ai besoin pour le diviser est de lire beaucoup d'autres gros fichiers et de rechercher les éléments de ces gros fichiers dans l'objet de consultation.

Nouvelle mise à jour : la base de données est une bonne solution, memcached pourrait être une meilleure solution, et le fichier sur disque (shelve ou dbm) pourrait être encore meilleur. Dans cette question, j'étais particulièrement intéressé par une solution en mémoire. Pour la solution finale, j'utiliserai Hadoop, mais je voulais voir si je pouvais aussi avoir une version locale en mémoire.

0 votes

Votre code tel qu'il est écrit appellera marshal.load pour le parent et pour chaque enfant (chaque processus importe le module).

0 votes

Tu as raison, corrigé.

0 votes

Pour le "local en mémoire" et si vous voulez éviter la copie, ce qui suit pourrait être utile docs.python.org/library/

56voto

S.Lott Points 207588

Les processus enfants engendrés par le multiprocessing partagent-ils des objets créés plus tôt dans le programme ?

Non pour Python < 3.8, oui pour Python ≥ 3.8 .

Les processus ont un espace mémoire indépendant.

Solution 1

Pour tirer le meilleur parti d'une grande structure avec beaucoup de travailleurs, faites ceci.

  1. Écrivez chaque travailleur comme un "filtre" - lit les résultats intermédiaires à partir de stdin fait le travail, écrit les résultats intermédiaires sur stdout .

  2. Connectez tous les travailleurs comme un pipeline :

    process1 <source | process2 | process3 | ... | processn >result

Chaque processus lit, effectue des travaux et écrit.

Cette méthode est remarquablement efficace puisque tous les processus sont exécutés simultanément. Les écritures et les lectures passent directement par des tampons partagés entre les processus.


Solution 2

Dans certains cas, vous avez une structure plus complexe - souvent une sortie en éventail structure. Dans ce cas, vous avez un parent avec plusieurs enfants.

  1. Le parent ouvre les données sources. Le parent bifurque vers un certain nombre d'enfants.

  2. Le parent lit la source, ferme des parties de la source à chaque enfant fonctionnant simultanément.

  3. Lorsque le parent atteint la fin, fermez le tuyau. L'enfant obtient la fin du fichier et termine normalement.

Les parties enfant sont agréables à écrire car chaque enfant lit simplement sys.stdin .

Le parent doit faire preuve d'un peu de fantaisie pour faire naître tous les enfants et conserver les tuyaux correctement, mais ce n'est pas trop grave.

Fan-in est la structure opposée. Un certain nombre de processus fonctionnant indépendamment doivent entrelacer leurs entrées dans un processus commun. Le collecteur n'est pas aussi facile à écrire, car il doit lire à partir de nombreuses sources.

La lecture de plusieurs tuyaux nommés est souvent effectuée à l'aide de la fonction select pour voir quels tuyaux ont une entrée en attente.


Solution 3

La consultation partagée est la définition d'une base de données.

Solution 3A - charger une base de données. Laissez les travailleurs traiter les données de la base.

Solution 3B - créer un serveur très simple en utilisant werkzeug (ou similaire) pour fournir des applications WSGI qui répondent à HTTP GET afin que les travailleurs puissent interroger le serveur.


Solution 4

Objet de système de fichiers partagé. Le système d'exploitation Unix propose des objets à mémoire partagée. Il s'agit simplement de fichiers qui sont mappés à la mémoire de façon à ce que les E/S de swapping soient effectuées au lieu des lectures tamponnées plus conventionnelles.

À partir d'un contexte Python, vous pouvez le faire de plusieurs façons

  1. Écrivez un programme de démarrage qui (1) divise votre objet gigantesque d'origine en objets plus petits, et (2) lance des travailleurs, chacun avec un objet plus petit. Les objets plus petits pourraient être des objets Python pickled pour gagner un tout petit peu de temps de lecture de fichier.

  2. Écrivez un programme de démarrage qui (1) lit votre objet gigantesque d'origine et écrit un fichier structuré en pages, codé en octets, utilisant seek pour faire en sorte que les différentes sections soient faciles à trouver par une simple recherche. C'est ce que fait un moteur de base de données : il divise les données en pages, rend chaque page facile à trouver grâce à un moteur de recherche. seek .

Créer des travailleurs ayant accès à ce grand fichier structuré en pages. Chaque travailleur peut rechercher les parties pertinentes et y effectuer son travail.

0 votes

Mes processus ne sont pas vraiment adaptés ; ils sont tous identiques, ils traitent simplement des données différentes.

0 votes

Ils peuvent souvent être structurés comme des filtres. Ils lisent leur morceau de données, font leur travail et écrivent leur résultat pour un traitement ultérieur.

0 votes

J'aime votre solution, mais que se passe-t-il avec les E/S de blocage ? Que se passe-t-il si le parent bloque la lecture/écriture de/vers l'un de ses enfants ? Select vous informe que vous pouvez écrire, mais il ne dit pas combien. Idem pour la lecture.

38voto

J.F. Sebastian Points 102961

Les processus enfants engendrés par le multitraitement partagent-ils les objets créés ? plus tôt dans le programme ?

Cela dépend. Pour les variables globales en lecture seule, on peut souvent considérer que c'est le cas (hormis la mémoire consommée), sinon il ne faut pas le faire.

multitraitement Selon la documentation de l'entreprise :

Better to inherit than pickle/unpickle

Sous Windows, de nombreux types de multiprocessing ont besoin d'être picklables afin que les processus enfants puissent les utiliser. Cependant, il faut généralement éviter d'envoyer des objets partagés à d'autres processus en utilisant des tuyaux ou des files d'attente. Au lieu de cela, vous devriez organiser le programme de sorte qu'un processus qui a besoin d'accéder à une ressource partagée créée ailleurs puisse en hériter d'un ancêtre processus.

Explicitly pass resources to child processes

Sous Unix, un processus enfant peut utiliser d'une ressource partagée créée dans un processus parent en utilisant une ressource globale. Cependant, il est préférable de passer l'objet comme argument au constructeur constructeur du processus enfant.

En plus de rendre le code (potentiellement) compatible avec Windows cela garantit également que tant que le est toujours en vie, l'objet l'objet ne sera pas ramassé dans le processus parent. Cela peut être important si une ressource est libérée lorsque l'objet est garbage collecté dans le processus parent.

Global variables

Gardez à l'esprit que si le code exécuté dans un processus enfant essaie d'accéder à une variable la valeur qu'il voit (si elle existe) valeur qu'il voit (le cas échéant) peut ne pas être la même que la valeur dans le processus parent au moment où Process.start() a été appelé.

Exemple

Sous Windows (un seul processeur) :

#!/usr/bin/env python
import os, sys, time
from multiprocessing import Pool

x = 23000 # replace `23` due to small integers share representation
z = []    # integers are immutable, let's try mutable object

def printx(y):
    global x
    if y == 3:
       x = -x
    z.append(y)
    print os.getpid(), x, id(x), z, id(z) 
    print y
    if len(sys.argv) == 2 and sys.argv[1] == "sleep":
       time.sleep(.1) # should make more apparant the effect

if __name__ == '__main__':
    pool = Pool(processes=4)
    pool.map(printx, (1,2,3,4))

Avec sleep :

$ python26 test_share.py sleep
2504 23000 11639492 [1] 10774408
1
2564 23000 11639492 [2] 10774408
2
2504 -23000 11639384 [1, 3] 10774408
3
4084 23000 11639492 [4] 10774408
4

Sans sleep :

$ python26 test_share.py
1148 23000 11639492 [1] 10774408
1
1148 23000 11639492 [1, 2] 10774408
2
1148 -23000 11639324 [1, 2, 3] 10774408
3
1148 -23000 11639324 [1, 2, 3, 4] 10774408
4

6 votes

Huh ? Comment z est partagé à travers les processus ???

5 votes

@cbare : Bonne question ! z n'est en fait pas partagé, comme le montre la sortie avec sleep. La sortie sans sleep montre que a simple Le processus gère (PID = 1148) tout le travail ; ce que nous voyons dans le dernier exemple est la valeur de z pour ce seul processus.

0 votes

Cette réponse montre que z n'est pas partagé. Ceci répond donc à la question par : "non, sous Windows du moins, une variable parent n'est pas partagée entre les enfants".

33voto

Jarret Hardie Points 36266

S.Lott est correct. Les raccourcis de multiprocessing de Python vous donnent effectivement un morceau de mémoire séparé et dupliqué.

Sur la plupart des systèmes *nix, l'utilisation d'un appel de niveau inférieur à os.fork() vous donnera, en fait, une mémoire de type "copy-on-write", ce qui est peut-être ce à quoi vous pensez. Je sais qu'en théorie, dans le programme le plus simpliste possible, vous pourriez lire ces données sans qu'elles soient dupliquées.

Cependant, les choses ne sont pas aussi simples dans l'interpréteur Python. Les données et les méta-données des objets sont stockées dans le même segment de mémoire, donc même si l'objet ne change jamais, quelque chose comme l'incrémentation du compteur de référence de cet objet provoquera une écriture en mémoire, et donc une copie. Presque tous les programmes Python qui font plus que "print 'hello'" provoqueront des incréments du compteur de référence, donc vous ne réaliserez probablement jamais l'avantage de la copie sur écriture.

Même si quelqu'un parvenait à pirater une solution de mémoire partagée en Python, essayer de coordonner la collecte des déchets entre les processus serait probablement assez pénible.

7 votes

Dans ce cas, seule la région de mémoire du compte de référence sera copiée, pas nécessairement les grandes données en lecture seule, n'est-ce pas ?

7voto

Jacob Gabrielson Points 8800

Si vous travaillez sous Unix, il se peut qu'ils partagent le même objet, à cause de comment fonctionne la fourche (c'est-à-dire que les processus enfants ont une mémoire séparée mais elle est en copie sur écriture, donc elle peut être partagée tant que personne ne la modifie). J'ai essayé ce qui suit :

import multiprocessing

x = 23

def printx(y):
    print x, id(x)
    print y

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=4)
    pool.map(printx, (1,2,3,4))

et j'ai obtenu le résultat suivant :

$ ./mtest.py
23 22995656
1
23 22995656
2
23 22995656
3
23 22995656
4

Bien sûr, cela ne prouver qu'une copie n'a pas été faite, mais vous devriez être capable de le vérifier dans votre situation en regardant la sortie de ps pour voir combien de mémoire réelle chaque sous-processus utilise.

2 votes

Et le ramasseur de déchets ? Que se passe-t-il lorsqu'il s'exécute ? La disposition de la mémoire ne change-t-elle pas ?

0 votes

C'est une préoccupation valable. Que cela affecte Parand dépend de la façon dont il utilise tout cela et de la fiabilité de ce code. Si cela ne fonctionnait pas pour lui, je lui recommanderais d'utiliser le module mmap pour plus de contrôle (en supposant qu'il veuille s'en tenir à cette approche de base).

0 votes

J'ai posté une mise à jour de votre exemple : stackoverflow.com/questions/659865/

3voto

Vasil Points 11172

Les différents processus ont un espace d'adressage différent. Comme l'exécution de différentes instances de l'interpréteur. C'est à ça que sert l'IPC (communication interprocessus).

Vous pouvez utiliser des files d'attente ou des tuyaux à cette fin. Vous pouvez également utiliser rpc over tcp si vous souhaitez distribuer les processus sur un réseau par la suite.

http://docs.python.org/dev/library/multiprocessing.html#exchanging-objects-between-processes

2 votes

Je ne pense pas que l'IPC soit approprié pour cela ; il s'agit de données en lecture seule auxquelles tout le monde doit avoir accès. Cela n'a aucun sens de les faire circuler entre les processus ; au pire, chacun peut lire sa propre copie. J'essaie d'économiser de la mémoire en n'ayant pas une copie séparée dans chaque processus.

0 votes

Vous pouvez avoir un processus maître qui délègue des éléments de données à travailler aux autres processus esclaves. Soit les esclaves peuvent demander des données, soit il peut les pousser. De cette façon, chaque processus n'aura pas une copie de l'objet entier.

1 votes

@Vasil : Que se passe-t-il si chaque processus a besoin de l'ensemble des données, et qu'il exécute simplement une opération différente sur celles-ci ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X