J'utilise Python multiprocessing, plus précisément
from multiprocessing import Pool
p = Pool(15)
args = [(df, config1), (df, config2), ...] #list of args - df is the same object in each Tuple
res = p.map_async(func, args) #func is some arbitrary function
p.close()
p.join()
Cette approche a une énorme consommation de mémoire; manger à peu près tout mon RAM (à ce moment, il devient extrêmement lent, ce qui rend le multitraitement assez inutile). Je suppose que le problème est que df
est un énorme objet ( un grand pandas dataframe) et il est copié pour chaque processus. J'ai essayé d'utiliser multiprocessing.Value
pour partager la trame de données sans copier
shared_df = multiprocessing.Value(pandas.DataFrame, df)
args = [(shared_df, config1), (shared_df, config2), ...]
(comme suggéré dans mémoire partagée multiprocessing Python ), mais cela me donne TypeError: this type has no size
(identique à Partager un objet complexe entre Python? , auquel je ne comprends malheureusement pas la réponse).
J'utilise le multitraitement pour la première fois et peut-être que ma compréhension n'est pas (encore) assez bonne. Est multiprocessing.Value
en fait même la bonne chose à utiliser dans ce cas? J'ai vu d'autres suggestions (par exemple, la file d'attente) mais je suis maintenant un peu confus. Quelles sont les options pour partager la mémoire et laquelle serait la meilleure dans ce cas?
Le premier argument de Value
est typecode_or_type. Cela se définit comme:
typecode_or_type détermine le type de l'objet renvoyé: il s'agit soit d'un type ctypes, soit d'un type typed'un caractère du type utilisé par le module tableau. * args est transmis au constructeur pour le type.
Je souligne. Donc, vous ne pouvez tout simplement pas mettre un pandas dataframe dans un Value
, il doit être n type ctypes .
Vous pouvez utiliser à la place un multiprocessing.Manager
pour servir votre instance de trame de données singleton à tous vos processus. Il y a plusieurs façons de se retrouver au même endroit - probablement la plus simple est de simplement placer votre trame de données dans le Namespace
du gestionnaire.
from multiprocessing import Manager
mgr = Manager()
ns = mgr.Namespace()
ns.df = my_dataframe
# now just give your processes access to ns, i.e. most simply
# p = Process(target=worker, args=(ns, work_unit))
Maintenant, votre instance de trame de données est accessible à tout processus qui obtient une référence au gestionnaire. Ou passez simplement une référence au Namespace
, c'est plus propre.
Une chose que je n'ai pas/ne couvrirai pas est les événements et la signalisation - si vos processus doivent attendre que d'autres finissent de s'exécuter, vous devrez l'ajouter. Voici une page avec certains Event
exemples qui couvrent également avec un peu plus de détails comment utiliser le Namespace
du gestionnaire.
(notez que rien de tout cela ne permet de savoir si multiprocessing
va entraîner des avantages tangibles en termes de performances, cela vous donne juste les outils pour explorer cette question)
Vous pouvez partager un pandas dataframe entre des processus sans surcharge de mémoire en créant un processus enfant data_handler. Ce processus reçoit des appels des autres enfants avec des demandes de données spécifiques (c.-à-d. Une ligne, une cellule spécifique, un tranche, etc.) à partir de votre très grand objet de trame de données. Seul le processus data_handler conserve votre trame de données en mémoire contrairement à un gestionnaire comme un espace de noms qui entraîne la copie de la trame de données dans tous les processus enfants. Voir ci-dessous pour un exemple de travail. Cela peut être converti en bassin.
Besoin d'une barre de progression pour cela? voir ma réponse ici: https://stackoverflow.com/a/55305714/11186769
import time
import Queue
import numpy as np
import pandas as pd
import multiprocessing
from random import randint
#==========================================================
# DATA HANDLER
#==========================================================
def data_handler( queue_c, queue_r, queue_d, n_processes ):
# Create a big dataframe
big_df = pd.DataFrame(np.random.randint(
0,100,size=(100, 4)), columns=list('ABCD'))
# Handle data requests
finished = 0
while finished < n_processes:
try:
# Get the index we sent in
idx = queue_c.get(False)
except Queue.Empty:
continue
else:
if idx == 'finished':
finished += 1
else:
try:
# Use the big_df here!
B_data = big_df.loc[ idx, 'B' ]
# Send back some data
queue_r.put(B_data)
except:
pass
# big_df may need to be deleted at the end.
#import gc; del big_df; gc.collect()
#==========================================================
# PROCESS DATA
#==========================================================
def process_data( queue_c, queue_r, queue_d):
data = []
# Save computer memory with a generator
generator = ( randint(0,x) for x in range(100) )
for g in generator:
"""
Lets make a request by sending
in the index of the data we want.
Keep in mind you may receive another
child processes return call, which is
fine if order isnt important.
"""
#print(g)
# Send an index value
queue_c.put(g)
# Handle the return call
while True:
try:
return_call = queue_r.get(False)
except Queue.Empty:
continue
else:
data.append(return_call)
break
queue_c.put('finished')
queue_d.put(data)
#==========================================================
# START MULTIPROCESSING
#==========================================================
def multiprocess( n_processes ):
combined = []
processes = []
# Create queues
queue_data = multiprocessing.Queue()
queue_call = multiprocessing.Queue()
queue_receive = multiprocessing.Queue()
for process in range(n_processes):
if process == 0:
# Load your data_handler once here
p = multiprocessing.Process(target = data_handler,
args=(queue_call, queue_receive, queue_data, n_processes))
processes.append(p)
p.start()
p = multiprocessing.Process(target = process_data,
args=(queue_call, queue_receive, queue_data))
processes.append(p)
p.start()
for i in range(n_processes):
data_list = queue_data.get()
combined += data_list
for p in processes:
p.join()
# Your B values
print(combined)
if __name__ == "__main__":
multiprocess( n_processes = 4 )