web-dev-qa-db-fra.com

Comment paralléliser une simple boucle Python?

C'est probablement une question triviale, mais comment puis-je paralléliser la boucle suivante en python?

# setup output lists
output1 = list()
output2 = list()
output3 = list()

for j in range(0, 10):
    # calc individual parameter value
    parameter = j * offset
    # call the calculation
    out1, out2, out3 = calc_stuff(parameter = parameter)

    # put results into correct output list
    output1.append(out1)
    output2.append(out2)
    output3.append(out3)

Je sais comment démarrer des threads simples dans Python mais je ne sais pas comment "collecter" les résultats.

Plusieurs processus conviendraient également - tout ce qui est le plus simple pour ce cas. J'utilise actuellement Linux, mais le code devrait également fonctionner sous Windows et Mac.

Quel est le moyen le plus simple de paralléliser ce code?

188
memyself

L'utilisation de plusieurs threads sur CPython ne vous donnera pas de meilleures performances pour le code pur-Python en raison du verrou d'interpréteur global (GIL). Je suggère d'utiliser le module multiprocessing à la place:

pool = multiprocessing.Pool(4)
out1, out2, out3 = Zip(*pool.map(calc_stuff, range(0, 10 * offset, offset)))

Notez que cela ne fonctionnera pas dans l'interpréteur interactif.

Pour éviter le FUD habituel autour du GIL: L'utilisation de threads pour cet exemple ne présente aucun avantage. Vous voulez utiliser des processus ici, pas des threads, car ils évitent toute une série de problèmes.

146
Sven Marnach

Pour paralléliser une simple boucle for, joblib apporte une grande valeur à l’utilisation brute du multitraitement. Non seulement la syntaxe courte, mais aussi des choses telles que le regroupement transparent d'itérations lorsqu'elles sont très rapides (pour supprimer la surcharge) ou la capture de la trace du processus enfant pour obtenir un meilleur rapport d'erreurs.

Disclaimer: Je suis l'auteur original de joblib.

48
Gael Varoquaux

Quel est le moyen le plus simple de paralléliser ce code?

J'aime beaucoup concurrent.futures pour cela, disponible en Python3 depuis la version 3.2 - et via backport vers 2.6 et 2.7 sur PyPi .

Vous pouvez utiliser des threads ou des processus et utiliser exactement la même interface.

Multitraitement

Mettez ceci dans un fichier - futuretest.py:

import concurrent.futures
import time, random               # add some random sleep time

offset = 2                        # you don't supply these so
def calc_stuff(parameter=None):   # these are examples.
    sleep_time = random.choice([0, 1, 2, 3, 4, 5])
    time.sleep(sleep_time)
    return parameter / 2, sleep_time, parameter * parameter

def procedure(j):                 # just factoring out the
    parameter = j * offset        # procedure
    # call the calculation
    return calc_stuff(parameter=parameter)

def main():
    output1 = list()
    output2 = list()
    output3 = list()
    start = time.time()           # let's see how long this takes

    # we can swap out ProcessPoolExecutor for ThreadPoolExecutor
    with concurrent.futures.ProcessPoolExecutor() as executor:
        for out1, out2, out3 in executor.map(procedure, range(0, 10)):
            # put results into correct output list
            output1.append(out1)
            output2.append(out2)
            output3.append(out3)
    finish = time.time()
    # these kinds of format strings are only available on Python 3.6:
    # time to upgrade!
    print(f'original inputs: {repr(output1)}')
    print(f'total time to execute {sum(output2)} = sum({repr(output2)})')
    print(f'time saved by parallelizing: {sum(output2) - (finish-start)}')
    print(f'returned in order given: {repr(output3)}')

if __== '__main__':
    main()

Et voici la sortie:

$ python3 -m futuretest
original inputs: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
total time to execute 33 = sum([0, 3, 3, 4, 3, 5, 1, 5, 5, 4])
time saved by parallellizing: 27.68999981880188
returned in order given: [0, 4, 16, 36, 64, 100, 144, 196, 256, 324]

Multithreading

Remplacez maintenant ProcessPoolExecutor par ThreadPoolExecutor, puis exécutez à nouveau le module:

$ python3 -m futuretest
original inputs: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
total time to execute 19 = sum([0, 2, 3, 5, 2, 0, 0, 3, 3, 1])
time saved by parallellizing: 13.992000102996826
returned in order given: [0, 4, 16, 36, 64, 100, 144, 196, 256, 324]

Maintenant, vous avez fait du multithreading et du multitraitement!

Remarque sur les performances et l'utilisation simultanée des deux.

L'échantillonnage est beaucoup trop petit pour comparer les résultats.

Cependant, je soupçonne que le multithreading sera plus rapide que le multitraitement en général, en particulier sous Windows, car Windows ne prend pas en charge la conversion, de sorte que chaque nouveau processus doit prendre du temps à se lancer. Sous Linux ou Mac, ils seront probablement plus proches.

Vous pouvez imbriquer plusieurs threads dans plusieurs processus, mais il est recommandé de ne pas utiliser plusieurs threads pour créer plusieurs processus.

39
Aaron Hall
from joblib import Parallel, delayed
import multiprocessing

inputs = range(10) 
def processInput(i):
    return i * i

num_cores = multiprocessing.cpu_count()

results = Parallel(n_jobs=num_cores)(delayed(processInput)(i) for i in inputs)
print(results)

Ce qui précède fonctionne à merveille sur ma machine (Ubuntu, le paquet joblib était pré-installé, mais peut être installé via pip install joblib).

Tiré de https://blog.dominodatalab.com/simple-parallelization/

21
tyrex

Il y a un certain nombre d'avantages à utiliser Ray :

  • Vous pouvez paralléliser sur plusieurs machines en plus de plusieurs cœurs (avec le même code).
  • Traitement efficace des données numériques via la mémoire partagée (et la sérialisation sans copie).
  • Débit de tâches élevé avec planification répartie.
  • Tolérance aux pannes.

Dans votre cas, vous pouvez démarrer Ray et définir une fonction distante.

import ray

ray.init()

@ray.remote(num_return_vals=3)
def calc_stuff(parameter=None):
    # Do something.
    return 1, 2, 3

puis invoquer en parallèle

output1, output2, output3 = [], [], []

# Launch the tasks.
for j in range(10):
    id1, id2, id3 = calc_stuff.remote(parameter=j)
    output1.append(id1)
    output2.append(id2)
    output3.append(id3)

# Block until the results have finished and get the results.
output1 = ray.get(output1)
output2 = ray.get(output2)
output3 = ray.get(output3)

Pour exécuter le même exemple sur un cluster, la seule ligne qui serait modifiée serait l'appel à ray.init (). La documentation pertinente peut être trouvée ici .

Notez que j'aide à développer Ray.

8
Robert Nishihara

J'ai trouvé que joblib est très utile avec moi. S'il vous plaît voir exemple suivant:

from joblib import Parallel, delayed
def yourfunction(k):   
    s=3.14*k*k
    print "Area of a circle with a radius ", k, " is:", s

element_run = Parallel(n_jobs=-1)(delayed(yourfunction)(k) for k in range(1,10))

n_jobs = -1: utiliser tous les cœurs disponibles

4
miuxu

pourquoi n'utilisez-vous pas de threads et un mutex pour protéger une liste globale?

import os
import re
import time
import sys
import thread

from threading import Thread

class thread_it(Thread):
    def __init__ (self,param):
        Thread.__init__(self)
        self.param = param
    def run(self):
        mutex.acquire()
        output.append(calc_stuff(self.param))
        mutex.release()   


threads = []
output = []
mutex = thread.allocate_lock()

for j in range(0, 10):
    current = thread_it(j * offset)
    threads.append(current)
    current.start()

for t in threads:
    t.join()

#here you have output list filled with data

gardez à l'esprit, vous serez aussi rapide que votre fil le plus lent

4
jackdoe

exemple très simple de traitement en parallèle est

from multiprocessing import Process

output1 = list()
output2 = list()
output3 = list()

def yourfunction():
    for j in range(0, 10):
        # calc individual parameter value
        parameter = j * offset
        # call the calculation
        out1, out2, out3 = calc_stuff(parameter=parameter)

        # put results into correct output list
        output1.append(out1)
        output2.append(out2)
        output3.append(out3)

if __== '__main__':
    p = Process(target=pa.yourfunction, args=('bob',))
    p.start()
    p.join()
2
Adil Warsi

Disons que nous avons une fonction asynchrone

async def work_async(self, student_name: str, code: str, loop):
"""
Some async function
"""
    # Do some async procesing    

Cela doit être exécuté sur un grand tableau. Certains attributs sont en train d'être transmis au programme et d'autres sont utilisés à partir de la propriété de l'élément dictionary du tableau.

async def process_students(self, student_name: str, loop):
    market = sys.argv[2]
    subjects = [...] #Some large array
    batchsize = 5
    for i in range(0, len(subjects), batchsize):
        batch = subjects[i:i+batchsize]
        await asyncio.gather(*(self.work_async(student_name,
                                           sub['Code'],
                                           loop)
                       for sub in batch))
1
Amit Teli

Cela pourrait être utile lors de la mise en œuvre de l'informatique multitraitement et parallèle/distribuée en Python.

tutoriel YouTube sur l'utilisation du paquet techila

Techila est un middleware informatique distribué, qui s'intègre directement à Python à l'aide du package techila. La fonction Peach du package peut être utile pour la parallélisation des structures de boucle. (L'extrait de code suivant provient de Forums de la communauté Techila )

techila.Peach(funcname = 'theheavyalgorithm', # Function that will be called on the compute nodes/ Workers
    files = 'theheavyalgorithm.py', # Python-file that will be sourced on Workers
    jobs = jobcount # Number of Jobs in the Project
    )
1
TEe

merci @iuryxavier

from multiprocessing import Pool
from multiprocessing import cpu_count


def add_1(x):
    return x + 1

if __== "__main__":
    pool = Pool(cpu_count())
    results = pool.map(add_1, range(10**12))
    pool.close()  # 'TERM'
    pool.join()   # 'KILL'
0
Felipe de Macêdo