web-dev-qa-db-fra.com

Comment paralléliser les calculs de compréhension de liste en Python?

La compréhension des listes et les calculs de carte devraient - du moins en théorie - être relativement faciles à paralléliser: chaque calcul dans une liste de compréhension pourrait être effectué indépendamment du calcul de tous les autres éléments. Par exemple dans l'expression

[ x*x for x in range(1000) ]

chaque x * x-Calcul pourrait (au moins en théorie) être effectué en parallèle.

Ma question est la suivante: existe-t-il un module/module Python/implémentation Python/Programmation-astuce Python permettant de paralléliser un calcul de compréhension de liste (afin d'utiliser tous les cœurs 16/32/... ou de répartir le calcul sur une grille d'ordinateur ou sur un nuage)?

38
phynfo

Comme Ken l'a dit, cela ne peut pas, mais avec 2.6 multitraitement module, il est assez facile de paralléliser les calculs.

import multiprocessing

try:
    cpus = multiprocessing.cpu_count()
except NotImplementedError:
    cpus = 2   # arbitrary default


def square(n):
    return n * n

pool = multiprocessing.Pool(processes=cpus)
print(pool.map(square, range(1000)))

Il existe également des exemples dans documentation qui montrent comment procéder à l'aide de Managers, ce qui devrait également permettre des calculs distribués. 

29

Sur la parallélisation automatique de la compréhension de liste

IMHO, effective la mise en parallèle automatique de la compréhension de liste serait impossible sans informations supplémentaires (telles que celles fournies à l'aide de directives dans OpenMP), ou en se limitant aux expressions qui impliquent uniquement des types/méthodes intégrés.

Sauf s'il est garanti que le traitement effectué sur chaque élément de la liste n'a aucun effet secondaire, il est possible que les résultats soient invalides (ou du moins différents) s'ils sont effectués dans le désordre.

# Artificial example
counter = 0

def g(x): # func with side-effect
    global counter
    counter = counter + 1
    return x + counter

vals = [g(i) for i in range(100)] # diff result when not done in order

Il y a aussi la question de la répartition des tâches. Comment l'espace-problème devrait-il être décomposé? 

Si le traitement de chaque élément forme une tâche (~ ferme de tâches), lorsqu'il y a de nombreux éléments impliquant chacun un calcul trivial, les frais généraux liés à la gestion des tâches annulent les gains de performances de la parallélisation. 

On pourrait également adopter l’approche de décomposition des données dans laquelle l’espace du problème est divisé également entre les processus disponibles. 

Le fait que la compréhension de liste fonctionne également avec les générateurs rend cette tâche un peu délicate, mais ce n’est probablement pas un obstacle à l’émission si les frais généraux liés à la pré-itération sont acceptables. Bien sûr, il est également possible que des générateurs avec des effets secondaires puissent modifier le résultat si les éléments suivants sont itérés prématurément. Très improbable, mais possible.

Un problème plus important serait le déséquilibre de charge entre les processus. Il n’existe aucune garantie que le traitement de chaque élément prenne la même quantité de temps. Par conséquent, les données partitionnées de manière statique peuvent faire en sorte qu’un processus effectue la majeure partie du travail pendant votre temps libre. 

Diviser la liste en morceaux plus petits et les distribuer à chaque fois qu'un processus enfant est disponible est un bon compromis. Cependant, une bonne sélection de taille de morceau dépend de l'application et n'est donc pas réalisable sans davantage d'informations de la part de l'utilisateur.

Des alternatives

Comme mentionné dans plusieurs autres réponses, il existe de nombreuses approches et modules/cadres de calcul parallèle à choisir en fonction de l'une des exigences.

N'ayant utilisé que MPI (en C) et n'ayant aucune expérience de l'utilisation de Python pour le traitement parallèle, je ne suis pas en mesure d'en garantir (bien que, après un balayage rapide, traitement multiple , pichet , pp et pyro se démarquent).

Si une exigence est de coller le plus près possible de la liste de compréhension, alors cruche semble être la correspondance la plus proche. Depuis le tutoriel , la répartition des tâches sur plusieurs instances peut être aussi simple que:

from jug.task import Task
from yourmodule import process_data
tasks = [Task(process_data,infile) for infile in glob('*.dat')]

Alors que cela a un effet similaire à multiprocessing.Pool.map(), jug peut utiliser différents moteurs pour synchroniser le processus et stocker les résultats intermédiaires (redis, système de fichiers, en mémoire), ce qui signifie que les processus peuvent s’étendre sur plusieurs nœuds d’un cluster.

8
Shawn Chin

L'utilisation des fonctions futures.{Thread,Process}PoolExecutor.map(func, *iterables, timeout=None) et futures.as_completed(future_instances, timeout=None) à partir du nouveau package 3.2 concurrent.futures pourrait aider.

Il est également disponible en version 2.6+ backport .

5
Georges Martin

Pour le parallélisme en mémoire partagée, je recommande joblib :

from joblib import delayed, Parallel

def square(x): return x*x
values = Parallel(n_jobs=NUM_CPUS)(delayed(square)(x) for x in range(1000))
4
Fred Foo

Non, car la compréhension de liste elle-même est une sorte de macro optimisée pour le langage C. Si vous le sortez et le parallélisez, alors ce n'est pas une compréhension de liste, c'est juste un bon vieux démodé MapReduce .

Mais vous pouvez facilement paralléliser votre exemple. Voici un bon tutoriel sur l’utilisation de MapReduce avec la bibliothèque de parallélisation de Python:

http://mikecvet.wordpress.com/2010/07/02/parallel-mapreduce-in-python/

3
Ken Kinder

Il existe une liste complète des paquets parallèles pour Python ici:

http://wiki.python.org/moin/ParallelProcessing

Je ne suis pas sûr que quiconque gère directement le fractionnement d'une structure de compréhension de liste, mais il devrait être trivial de formuler le même problème de manière non compréhensible par une liste, ce qui peut facilement être acheminé vers un certain nombre de processeurs différents. Je ne suis pas familier avec la parallélisation en nuage, mais j'ai connu un certain succès avec mpi4py sur des machines multicœurs et sur des clusters. La plus grande question à laquelle vous devrez réfléchir est de savoir si la surcharge de communication va supprimer tous les avantages que vous obtenez de la parallélisation du problème. 

Edit: Ce qui suit pourrait également être intéressant:

http://www.mblondel.org/journal/2009/11/27/easy-parallelization-with-data-decomposition/

1
JoshAdel

Pas dans une liste de compréhension autant que je sache.

Vous pouvez certainement le faire avec une boucle for traditionnelle et les modules de multitraitement/threading.

1
mluebke

Comme le soulignent les réponses ci-dessus, il est en fait assez difficile à faire automatiquement. Ensuite, je pense que la question est de savoir comment le faire de la manière la plus simple possible. Idéalement, une solution ne nécessiterait pas que vous sachiez des choses comme "combien de cœurs ai-je" Une autre propriété que vous pouvez souhaiter est de pouvoir continuer à comprendre la liste dans une seule ligne lisible.

Certaines des réponses données semblent déjà avoir des propriétés Nice comme celle-ci, mais une autre alternative est Ray ( docs ), qui est un cadre pour l’écriture en Python parallèle. Dans Ray, vous le feriez comme ceci:

import ray

# Start Ray. This creates some processes that can do work in parallel.
ray.init()

# Add this line to signify that the function can be run in parallel (as a
# "task"). Ray will load-balance different `square` tasks automatically.
@ray.remote
def square(x):
    return x * x

# Create some parallel work using a list comprehension, then block until the
# results are ready with `ray.get`.
ray.get([square.remote(x) for x in range(1000)])
1
Stephanie Wang