web-dev-qa-db-fra.com

Python multiprocessing: Comment savoir utiliser Pool ou Process?

J'ai donc un algorithme que j'écris, et la fonction multiprocess est censée appeler une autre fonction, CreateMatrixMp(), sur autant de processus qu'il y a de cpus, en parallèle. Je n'ai jamais fait de multitraitement auparavant et je ne peux pas être sûr de laquelle des méthodes ci-dessous est plus efficace. Le mot "efficace" étant utilisé dans le contexte de la fonction CreateMatrixMp() devant potentiellement être appelée des milliers de fois. J'ai lu toute la documentation sur le python multiprocessing module, et sont arrivés à ces deux possibilités:

La première consiste à utiliser la classe Pool:

def MatrixHelper(self, args):
    return self.CreateMatrix(*args)

def Multiprocess(self, sigmaI, sigmaX):

    cpus = mp.cpu_count()
    print('Number of cpu\'s to process WM: %d' % cpus)
    poolCount = cpus*2
    args = [(sigmaI, sigmaX, i) for i in range(self.numPixels)]

    pool = mp.Pool(processes = poolCount, maxtasksperchild= 2)
    tempData = pool.map(self.MatrixHelper, args)
    pool.close()
    pool.join()

Et ensuite, utiliser la classe Process:

def Multiprocess(self, sigmaI, sigmaX):

    cpus = mp.cpu_count()
    print('Number of cpu\'s to process WM: %d' % cpus)

    processes = [mp.Process(target = self.CreateMatrixMp, args = (sigmaI, sigmaX, i,)) for i in range(self.numPixels)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()

Pool semble être le meilleur choix. J'ai lu que cela entraîne moins de frais généraux. Et Process ne prend pas en compte le nombre de cpus sur la machine. Le seul problème est que l'utilisation de Pool de cette manière me donne erreur après erreur, et chaque fois que j'en fixe un, il y en a un nouveau en dessous. Process semble plus facile à implémenter, et pour autant que je sache, c'est peut-être le meilleur choix. Que vous dit votre expérience?

Si Pool doit être utilisé, ai-je raison de choisir map()? Il serait préférable que l'ordre soit maintenu. J'ai tempData = pool.map(...) parce que la fonction map est censée retourner une liste des résultats de chaque processus. Je ne sais pas comment Process gère ses données renvoyées.

15
Anonymous

Je pense que la classe Pool est généralement plus pratique, mais cela dépend si vous voulez que vos résultats soient ordonnés ou non.

Supposons que vous souhaitiez créer 4 chaînes aléatoires (par exemple, il pourrait s'agir d'un générateur d'ID utilisateur aléatoire):

import multiprocessing as mp
import random
import string

# Define an output queue
output = mp.Queue()

# define a example function
def Rand_string(length, output):
    """ Generates a random string of numbers, lower- and uppercase chars. """
    Rand_str = ''.join(random.choice(
                    string.ascii_lowercase
                    + string.ascii_uppercase
                    + string.digits)
               for i in range(length))
    output.put(Rand_str)

# Setup a list of processes that we want to run
processes = [mp.Process(target=Rand_string, args=(5, output)) for x in range(4)]

# Run processes
for p in processes:
    p.start()

# Exit the completed processes
for p in processes:
    p.join()

# Get process results from the output queue
results = [output.get() for p in processes]

print(results)

# Output
# ['yzQfA', 'PQpqM', 'SHZYV', 'PSNkD']

Ici, l'ordre n'a probablement pas d'importance. Je ne sais pas s'il y a une meilleure façon de le faire, mais si je veux garder une trace des résultats dans l'ordre dans lequel les fonctions sont appelées, je retourne généralement des tuples avec un ID comme premier élément, par exemple,

# define a example function
def Rand_string(length, pos, output):
    """ Generates a random string of numbers, lower- and uppercase chars. """
    Rand_str = ''.join(random.choice(
                    string.ascii_lowercase
                    + string.ascii_uppercase
                    + string.digits)
                for i in range(length))
    output.put((pos, Rand_str))

# Setup a list of processes that we want to run
processes = [mp.Process(target=Rand_string, args=(5, x, output)) for x in range(4)]

print(processes)

# Output
# [(1, '5lUya'), (3, 'QQvLr'), (0, 'KAQo6'), (2, 'nj6Q0')]

Cela me permet ensuite de trier les résultats:

results.sort()
results = [r[1] for r in results]
print(results)

# Output:
# ['KAQo6', '5lUya', 'nj6Q0', 'QQvLr']

La classe Pool

Maintenant à votre question: en quoi est-ce différent de la classe Pool? Vous préférez généralement Pool.map pour retourner la liste ordonnée des résultats sans passer par le cercle de création de tuples et de les trier par ID. Ainsi, je dirais qu'il est généralement plus efficace.

def cube(x):
    return x**3

pool = mp.Pool(processes=4)
results = pool.map(cube, range(1,7))
print(results)

# output:
# [1, 8, 27, 64, 125, 216]

De manière équivalente, il existe également une méthode "appliquer":

pool = mp.Pool(processes=4)
results = [pool.apply(cube, args=(x,)) for x in range(1,7)]
print(results)

# output:
# [1, 8, 27, 64, 125, 216]

Tous les deux Pool.apply et Pool.map verrouillera le programme principal jusqu'à la fin d'un processus.

Maintenant, vous avez également Pool.apply_async et Pool.map_async, qui renvoie le résultat dès que le processus est terminé, ce qui est essentiellement similaire à la classe Process ci-dessus. L'avantage peut être qu'ils vous fournissent les fonctionnalités pratiques de apply et map que vous connaissez grâce à Python apply et map

16
user2489252

Vous pouvez facilement le faire avec pypeln :

import pypeln as pl

stage = pl.process.map(
    CreateMatrixMp, 
    range(self.numPixels), 
    workers=poolCount, 
    maxsize=2,
)

# iterate over it in the main process
for x in stage:
   # code

# or convert it to a list
data = list(stage)
1
Cristian Garcia