Supposons que j'ai un grand tableau numpy en mémoire, j'ai une fonction func
qui prend ce tableau géant en entrée (avec quelques autres paramètres). func
avec différents paramètres peuvent être exécutés en parallèle. Par exemple:
def func(arr, param):
# do stuff to arr, param
# build array arr
pool = Pool(processes = 6)
results = [pool.apply_async(func, [arr, param]) for param in all_params]
output = [res.get() for res in results]
Si j'utilise une bibliothèque multitraitement, ce tableau géant sera copié plusieurs fois dans différents processus.
Existe-t-il un moyen de laisser différents processus partager le même tableau? Cet objet tableau est en lecture seule et ne sera jamais modifié.
Ce qui est plus compliqué, si arr n'est pas un tableau, mais un objet Python arbitraire, y a-t-il un moyen de le partager?
[ÉDITÉ]
J'ai lu la réponse mais je suis encore un peu confus. Puisque fork () est une copie sur écriture, nous ne devrions pas invoquer de coût supplémentaire lors de la création de nouveaux processus dans la bibliothèque multiprocesseur python. Mais le code suivant suggère qu'il y a une surcharge énorme:
from multiprocessing import Pool, Manager
import numpy as np;
import time
def f(arr):
return len(arr)
t = time.time()
arr = np.arange(10000000)
print "construct array = ", time.time() - t;
pool = Pool(processes = 6)
t = time.time()
res = pool.apply_async(f, [arr,])
res.get()
print "multiprocessing overhead = ", time.time() - t;
sortie (et au fait, le coût augmente à mesure que la taille du tableau augmente, donc je soupçonne qu'il y a encore des frais généraux liés à la copie de mémoire):
construct array = 0.0178790092468
multiprocessing overhead = 0.252444982529
Pourquoi y a-t-il une telle surcharge si nous ne copions pas le tableau? Et quelle part la mémoire partagée me sauve-t-elle?
Réponses:
Si vous utilisez un système d'exploitation qui utilise la
fork()
sémantique de copie sur écriture (comme n'importe quel Unix commun), tant que vous ne modifiez jamais votre structure de données, il sera disponible pour tous les processus enfants sans prendre de mémoire supplémentaire. Vous n'aurez rien à faire de spécial (sauf assurez-vous de ne pas modifier l'objet).La chose la plus efficace que vous puissiez faire pour résoudre votre problème serait de regrouper votre tableau dans une structure de tableau efficace (en utilisant
numpy
ouarray
), de le placer dans la mémoire partagée, de l'enveloppermultiprocessing.Array
et de le transmettre à vos fonctions. Cette réponse montre comment faire cela .Si vous voulez un objet partagé inscriptible , vous devrez l'envelopper avec une sorte de synchronisation ou de verrouillage.
multiprocessing
fournit deux méthodes pour ce faire : l'une utilisant la mémoire partagée (adaptée pour des valeurs simples, des tableaux ou des ctypes) ou unManager
proxy, où un processus détient la mémoire et un gestionnaire arbitre l'accès à celle-ci à partir d'autres processus (même sur un réseau).L'
Manager
approche peut être utilisée avec des objets Python arbitraires, mais sera plus lente que l'équivalent utilisant la mémoire partagée car les objets doivent être sérialisés / désérialisés et envoyés entre les processus.Il existe une multitude de bibliothèques et d'approches de traitement parallèle disponibles en Python .
multiprocessing
est une bibliothèque excellente et bien équilibrée, mais si vous avez des besoins spéciaux, l'une des autres approches peut être meilleure.la source
apply_async
doit référencer l'objet partagé dans la portée directement plutôt que via ses arguments.J'ai rencontré le même problème et j'ai écrit une petite classe d'utilitaire de mémoire partagée pour le contourner.
J'utilise
multiprocessing.RawArray
(lockfree), et l'accès aux tableaux n'est pas du tout synchronisé (lockfree), faites attention de ne pas tirer vos propres pieds.Avec la solution, j'obtiens des accélérations d'un facteur d'environ 3 sur un i7 quad-core.
Voici le code: n'hésitez pas à l'utiliser et à l'améliorer, et veuillez signaler tout bogue.
la source
C'est le cas d'utilisation prévu pour Ray , qui est une bibliothèque pour Python parallèle et distribué. Sous le capot, il sérialise les objets à l'aide de la disposition des données Apache Arrow (qui est un format sans copie) et les stocke dans un magasin d'objets à mémoire partagée afin qu'ils soient accessibles par plusieurs processus sans créer de copies.
Le code ressemblerait à ce qui suit.
Si vous n'appelez pas,
ray.put
le tableau sera toujours stocké dans la mémoire partagée, mais cela sera fait une fois par appel defunc
, ce qui n'est pas ce que vous voulez.Notez que cela fonctionnera non seulement pour les tableaux mais aussi pour les objets qui contiennent des tableaux , par exemple, les dictionnaires mappant les entrées aux tableaux comme ci-dessous.
Vous pouvez comparer les performances de la sérialisation dans Ray par rapport à pickle en exécutant ce qui suit dans IPython.
La sérialisation avec Ray n'est que légèrement plus rapide que pickle, mais la désérialisation est 1000x plus rapide en raison de l'utilisation de la mémoire partagée (ce nombre dépendra bien sûr de l'objet).
Consultez la documentation Ray . Vous pouvez en savoir plus sur la sérialisation rapide à l'aide de Ray et Arrow . Notez que je suis l'un des développeurs Ray.
la source
Comme Robert Nishihara l'a mentionné, Apache Arrow rend cela facile, en particulier avec le magasin d'objets en mémoire Plasma, sur lequel Ray est construit.
J'ai créé du plasma cérébral spécifiquement pour cette raison: chargement et rechargement rapides de gros objets dans une application Flask. Il s'agit d'un espace de noms d'objets à mémoire partagée pour les objets sérialisables Apache Arrow, y compris
pickle
'd bytestrings générés parpickle.dumps(...)
.La principale différence avec Apache Ray et Plasma est qu'il assure le suivi des ID d'objet pour vous. Tous les processus, threads ou programmes qui s'exécutent localement peuvent partager les valeurs des variables en appelant le nom depuis n'importe quel
Brain
objet.la source