101 votes

Partager un grand tableau Numpy en lecture seule entre les processus de multitraitement

J'ai une 60GO SciPy Tableau (Matrice), je dois le partager entre les 5+ multitraitement Processus d'objets. J'ai vu numpy-sharedmem et de lire cette discussion sur le SciPy liste. Il semble y avoir deux approches -numpy-sharedmem et à l'aide d'un multiprocessing.RawArray() et de la cartographie NumPy dtypes de ctypes. Maintenant, numpy-sharedmem semble être la voie à suivre, mais je n'ai pas encore de voir un bon exemple de référence. Je n'ai pas besoin de tout type de serrures, depuis le tableau (en fait une matrice) sera en lecture seule. Maintenant, en raison de sa taille, je voudrais éviter une copie. Il sonne comme de la bonne méthode est de créer une seule copie du tableau en tant que sharedmem tableau, et ensuite de le passer au Processus d'objets? Un couple de questions précises:

  1. Quelle est la meilleure façon de passer le sharedmem poignées de sous-Process()es? Ai-je besoin d'une file d'attente juste pour passer un tableau? Aurait un tuyau de mieux? Puis-je passer en argument à la Process() sous-classe init (où je suis en supposant que c'est marinés)?

  2. Dans la discussion que j'ai lié ci-dessus, il est question de numpy-sharedmem de ne pas être en 64 bits, coffre-fort? Je suis définitivement à l'aide de certaines structures qui ne sont pas 32 bits adressables.

  3. Y sont compromis à l' RawArray() approche? Plus lent, buggier?

  4. Ai-je besoin ctype-à-dtype de cartographie pour la numpy-sharedmem méthode?

  5. Quelqu'un aurait-il un exemple de code OpenSource faire cela? Je suis un très pratiques apprises et c'est difficile de faire ce travail sans aucune sorte de bon exemple à regarder.

Si il y a d'autres infos que je peux fournir pour aider à clarifier la situation pour les autres, s'il vous plaît commentaire et je vais l'ajouter. Merci!

Ce besoin de fonctionner sur Ubuntu Linux et Peut-être que Mac OS, mais la portabilité n'est pas une préoccupation majeure.

45voto

Jan-Philip Gehrcke Points 6259

Si vous êtes sur Linux (ou tout conforme à POSIX système), vous pouvez définir ce tableau comme une variable globale. multiprocessing est à l'aide de fork() sur Linux quand il commence un nouveau processus enfant. A récemment donné naissance à l'enfant le processus de partage automatiquement la mémoire avec ses parents tant qu'il ne change pas (copy-on-write mécanisme).

Depuis que vous dites "je n'ai pas besoin de tout type de serrures, depuis le tableau (en fait une matrice) sera en lecture seule", profitant de ce comportement serait très simple et pourtant très efficace: tous les processus enfants auront accès aux mêmes données dans la mémoire physique lors de la lecture de ce grand tableau numpy.

Ne remettez pas votre tableau à l' Process() constructeur, cela permettra multiprocessing de pickle les données à l'enfant, ce qui serait extrêmement inefficace ou impossible dans votre cas. Sur Linux, juste après l' fork() l'enfant est une copie exacte de la mère à l'aide de la même mémoire physique, de sorte que tous vous avez besoin à faire est de s'assurer que la variable Python 'contenant' la matrice est accessible à partir de l' target de la fonction que vous remettez à l' Process(). Ce que vous pouvez généralement obtenir avec un 'global' variable.

Exemple de code:

from multiprocessing import Process
from numpy import random


global_array = random.random(10**4)


def child():
    print sum(global_array)


def main():
    processes = [Process(target=child) for _ in xrange(10)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()


if __name__ == "__main__":
    main()

Sur Windows -- qui ne prend pas en charge fork() -- multiprocessing est l'aide de l'API win32 appel CreateProcess. Il crée un tout nouveau processus à partir de tout fichier exécutable. C'est pourquoi sur Windows est requis pour mariner des données à l'enfant, si l'on a besoin de données qui a été créé au cours de l'exécution de la mère.

31voto

Jim Lim Points 4981

@Velimir Mlaker a donné une grande réponse. J'ai pensé que je pourrais ajouter quelques bribes de commentaires et un tout petit exemple.

(Je ne pouvais pas trouver beaucoup de documentation sur sharedmem - ce sont les résultats de mes propres expériences.)

  1. Avez-vous besoin de passer les poignées lorsque le sous-processus est le départ, ou après qu'il a commencé? Si c'est juste l'ancien, vous pouvez simplement utiliser l' target et args arguments pour Process. C'est potentiellement mieux que d'utiliser une variable globale.
  2. À partir de la page de discussion vous lié, il apparaît que le soutien pour Linux 64-bit a été ajouté à sharedmem un moment en arrière, de sorte qu'il pourrait être un non-problème.
  3. Je ne sais pas à propos de celui-ci.
  4. Pas de. Reportez-vous à l'exemple ci-dessous.

Exemple

#!/usr/bin/env python
from multiprocessing import Process
import sharedmem
import numpy

def do_work(data, start):
    data[start] = 0;

def split_work(num):
    n = 20
    width  = n/num
    shared = sharedmem.empty(n)
    shared[:] = numpy.random.rand(1, n)[0]
    print "values are %s" % shared

    processes = [Process(target=do_work, args=(shared, i*width)) for i in xrange(num)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()

    print "values are %s" % shared
    print "type is %s" % type(shared[0])

if __name__ == '__main__':
    split_work(4)

Sortie

values are [ 0.81397784  0.59667692  0.10761908  0.6736734   0.46349645  0.98340718
  0.44056863  0.10701816  0.67167752  0.29158274  0.22242552  0.14273156
  0.34912309  0.43812636  0.58484507  0.81697513  0.57758441  0.4284959
  0.7292129   0.06063283]
values are [ 0.          0.59667692  0.10761908  0.6736734   0.46349645  0.
  0.44056863  0.10701816  0.67167752  0.29158274  0.          0.14273156
  0.34912309  0.43812636  0.58484507  0.          0.57758441  0.4284959
  0.7292129   0.06063283]
type is <type 'numpy.float64'>

Cette question connexe pourrait être utile.

26voto

Velimir Mlaker Points 1419

Vous pouvez être intéressé par un petit morceau de code que j'ai écrit: github.com/vmlaker/benchmark-sharedmem

Le seul fichier de l'intérêt est - main.py. C'est un point de référence de numpy-sharedmem , le code passe simplement des tableaux ( numpy ou sharedmem) pour les processus lancés, via un Tuyau. Les travailleurs, appel sum() sur les données. J'était seulement intéressé par la comparaison de la communication des données à la fois entre les deux implémentations.

J'ai également écrit un autre, un code plus complexe: github.com/vmlaker/sherlock.

J'utilise ici la numpy-sharedmem module en temps réel le traitement de l'image avec OpenCV-les images sont des tableaux NumPy, comme par OpenCV la plus récente de l' cv2 API. Les images, en fait les références de celle-ci, sont partagés entre processus via le dictionnaire d'objet créé à partir d' multiprocessing.Manager (par opposition à l'utilisation de la File d'attente ou un Tuyau.) Je suis une amélioration importante des performances en comparaison avec l'aide de la plaine des tableaux NumPy.

Pipe vs File d'attente:

Dans mon expérience, la CIB avec le Tuyau est plus rapide que la File d'attente. Et qui a du sens, puisque la File d'attente ajoute de verrouillage pour le rendre sûr pour plusieurs producteurs/consommateurs. La Pipe n'a pas d'. Mais si vous avez seulement deux procédés de parler de retour-et-vient, c'est sûr à utiliser Pipe, ou, comme les docs de lecture:

... il n'y a pas de risque de corruption de processus à l'aide de différentes extrémités de la conduite en même temps.

sharedmem sécurité:

Le principal problème avec les sharedmem module est la possibilité de fuite de mémoire lors de la disgracieux sortie du programme. Ceci est décrit dans un long débat ici. Bien que sur Avr 10, 2011 Sturla mentionne un correctif de fuite de mémoire, j'ai toujours connu des fuites depuis, en utilisant à la fois les pensions de titres, Sturla, Molden propre sur GitHub (github.com/sturlamolden/sharedmem-numpy) et Chris Lee-Messer sur Bitbucket (bitbucket.org/cleemesser/numpy-sharedmem).

18voto

Saullo Castro Points 12260

Si votre tableau est que vous pouvez utiliser numpy.memmap. Par exemple, si vous avez un tableau stocké dans le disque, 'test.array', vous pouvez utiliser des processus simultanés d'accéder aux données en elle même en "écriture" mode, mais votre cas est plus simple puisque vous n'avez besoin que la "lecture" de mode.

La création de la matrice:

a = np.memmap('test.array', dtype='float32', mode='w+', shape=(100000,1000))

Vous pouvez alors remplir ce tableau de la même manière que vous le faites avec un tableau ordinaire. Par exemple:

a[:10,:100]=1.
a[10:,100:]=2.

Les données sont stockées dans le disque lorsque vous supprimez la variable a.

Plus tard, vous pouvez utiliser plusieurs processus qui permettront d'accéder aux données en test.array:

# read-only mode
b = np.memmap('test.array', dtype='float32', mode='r', shape=(100000,1000))

# read and writing mode
c = np.memmap('test.array', dtype='float32', mode='r+', shape=(100000,1000))

Réponses:

3voto

Steve Barnes Points 9505

Vous pouvez également trouver utile de consulter la documentation de pyro car si vous pouviez partitionner votre tâche correctement, vous pourriez l'utiliser pour exécuter différentes sections sur différentes machines ainsi que sur différents cœurs de la même machine.

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