Come eseguire la programmazione parallela in Python?


141

Per C ++, possiamo usare OpenMP per eseguire la programmazione parallela; tuttavia, OpenMP non funzionerà per Python. Cosa devo fare se voglio mettere in parallelo alcune parti del mio programma Python?

La struttura del codice può essere considerata come:

solve1(A)
solve2(B)

Dove solve1e solve2sono due funzioni indipendenti. Come eseguire questo tipo di codice in parallelo anziché in sequenza per ridurre il tempo di esecuzione? Spero che qualcuno mi possa aiutare. Grazie mille in anticipo. Il codice è:

def solve(Q, G, n):
    i = 0
    tol = 10 ** -4

    while i < 1000:
        inneropt, partition, x = setinner(Q, G, n)
        outeropt = setouter(Q, G, n)

        if (outeropt - inneropt) / (1 + abs(outeropt) + abs(inneropt)) < tol:
            break

        node1 = partition[0]
        node2 = partition[1]

        G = updateGraph(G, node1, node2)

        if i == 999:
            print "Maximum iteration reaches"
    print inneropt

Dove setinner e setouter sono due funzioni indipendenti. Ecco dove voglio mettere in parallelo ...


31
Dai un'occhiata al multiprocessing . Nota: i thread di Python non sono adatti per attività associate alla CPU, ma solo per I / O-bound.
9000

4
@ 9000 +100 Internet per menzionare le attività dipendenti dalla CPU rispetto agli I / O.
Hyperboreus,

@ 9000 In realtà i thread non sono adatti per attività legate alla CPU, per quanto ne so! I processi sono la strada da percorrere quando si eseguono attività legate alla CPU reali.
Omar Al-Ithawi,

6
@OmarIthawi: perché, i thread funzionano bene se hai molti core della CPU (come al solito ora). Quindi il processo può eseguire diversi thread caricando tutti questi core in parallelo e condividendo implicitamente dati comuni tra loro (ovvero senza un'area di memoria condivisa esplicita o messaggi tra processi).
9000

1
@ user2134774: Beh, sì, il mio secondo commento ha poco senso. Probabilmente le sole estensioni C che rilasciano il GIL possono trarne vantaggio; ad esempio parti di NumPy e Panda lo fanno. In altri casi, è sbagliato (ma non posso modificarlo ora).
9000,

Risposte:


162

È possibile utilizzare il modulo multiprocessing . Per questo caso potrei usare un pool di elaborazione:

from multiprocessing import Pool
pool = Pool()
result1 = pool.apply_async(solve1, [A])    # evaluate "solve1(A)" asynchronously
result2 = pool.apply_async(solve2, [B])    # evaluate "solve2(B)" asynchronously
answer1 = result1.get(timeout=10)
answer2 = result2.get(timeout=10)

Questo genererà processi che possono fare un lavoro generico per te. Dal momento che non abbiamo superato processes, genererà un processo per ciascun core della CPU sulla tua macchina. Ogni core della CPU può eseguire un processo contemporaneamente.

Se si desidera mappare un elenco su una singola funzione, è necessario:

args = [A, B]
results = pool.map(solve1, args)

Non usare i thread perché GIL blocca qualsiasi operazione sugli oggetti Python.


1
fa pool.mapaccetta anche dizionari come args? O solo elenchi semplici?
Il Bndr il

Solo elenchi penso. Ma puoi semplicemente passare a dict.items () che sarà un elenco di tuple chiave-valore
Matt Williamson,

Sfortunatamente questo termina con un tipo "non lavabile: errore" list "
The Bndr

oltre al mio ultimo commento: lavoro `dict.items ()`. L'errore aumenta, perché ho dovuto modificare la gestione della variabile insight in process-funktion. Sfortunatamente il messaggio di errore non è stato molto utile ... Quindi: grazie per il tuo suggerimento. :-)
Il Bndr il

2
Che cos'è il timeout qui?
gamma

26

Questo può essere fatto in modo molto elegante con Ray .

Per parallelizzare il tuo esempio, dovresti definire le tue funzioni con il @ray.remotedecoratore e quindi invocarle .remote.

import ray

ray.init()

# Define the functions.

@ray.remote
def solve1(a):
    return 1

@ray.remote
def solve2(b):
    return 2

# Start two tasks in the background.
x_id = solve1.remote(0)
y_id = solve2.remote(1)

# Block until the tasks are done and get the results.
x, y = ray.get([x_id, y_id])

Ci sono una serie di vantaggi rispetto al modulo multiprocessing .

  1. Lo stesso codice verrà eseguito su una macchina multicore e su un cluster di macchine.
  2. I processi condividono i dati in modo efficiente attraverso la memoria condivisa e la serializzazione a zero copie .
  3. I messaggi di errore si propagano bene.
  4. Queste chiamate di funzione possono essere composte insieme, ad es.

    @ray.remote
    def f(x):
        return x + 1
    
    x_id = f.remote(1)
    y_id = f.remote(x_id)
    z_id = f.remote(y_id)
    ray.get(z_id)  # returns 4
  5. Oltre a richiamare le funzioni in remoto, le classi possono essere istanziate in remoto come attori .

Nota che Ray è un framework che sto aiutando a sviluppare.


continuo a ricevere un errore che dice "Impossibile trovare una versione che soddisfi il requisito ray (dalle versioni
:)

2
Di solito questo tipo di errore indica che è necessario eseguire l'aggiornamento pip. Suggerirei di provare pip install --upgrade pip. Se è necessario utilizzarlo sudo, è possibile che la versione piputilizzata per l'installazione raynon sia la stessa che viene aggiornata. Puoi verificare con pip --version. Inoltre, Windows non è attualmente supportato, quindi se sei su Windows questo è probabilmente il problema.
Robert Nishihara,

1
Solo una nota, questo è principalmente per la distribuzione di lavori simultanei su più macchine.
Matt Williamson,

2
In realtà è ottimizzato sia per il caso di macchina singola che per l'impostazione del cluster. Molte delle decisioni di progettazione (ad es. Memoria condivisa, serializzazione zero-copy) mirano a supportare bene singole macchine.
Robert Nishihara,

2
Sarebbe bello se i documenti lo segnalassero di più. Ho capito dalla lettura dei documenti che non era realmente destinato al caso della macchina singola.
Slitta,


4

La soluzione, come altri hanno già detto, è utilizzare processi multipli. Quale quadro è più appropriato, tuttavia, dipende da molti fattori. Oltre a quelli già menzionati, ci sono anche charm4py e mpi4py (sono lo sviluppatore di charm4py).

Esiste un modo più efficiente per implementare l'esempio sopra che usare l'astrazione del pool di lavoratori. Il ciclo principale invia gli stessi parametri (incluso il grafico completo G) più e più volte ai lavoratori in ciascuna delle 1000 iterazioni. Poiché almeno un lavoratore risiederà in un processo diverso, ciò comporta la copia e l'invio degli argomenti agli altri processi. Questo potrebbe essere molto costoso a seconda della dimensione degli oggetti. Al contrario, ha senso che i lavoratori memorizzino lo stato e inviino semplicemente le informazioni aggiornate.

Ad esempio, in charm4py questo può essere fatto in questo modo:

class Worker(Chare):

    def __init__(self, Q, G, n):
        self.G = G
        ...

    def setinner(self, node1, node2):
        self.updateGraph(node1, node2)
        ...


def solve(Q, G, n):
    # create 2 workers, each on a different process, passing the initial state
    worker_a = Chare(Worker, onPE=0, args=[Q, G, n])
    worker_b = Chare(Worker, onPE=1, args=[Q, G, n])
    while i < 1000:
        result_a = worker_a.setinner(node1, node2, ret=True)  # execute setinner on worker A
        result_b = worker_b.setouter(node1, node2, ret=True)  # execute setouter on worker B

        inneropt, partition, x = result_a.get()  # wait for result from worker A
        outeropt = result_b.get()  # wait for result from worker B
        ...

Nota che per questo esempio abbiamo davvero bisogno di un solo lavoratore. Il ciclo principale potrebbe eseguire una delle funzioni e fare in modo che il lavoratore esegua l'altra. Ma il mio codice aiuta a illustrare un paio di cose:

  1. Il lavoratore A viene eseguito nel processo 0 (uguale al ciclo principale). Mentre result_a.get()è bloccato in attesa del risultato, il lavoratore A esegue il calcolo nello stesso processo.
  2. Gli argomenti vengono passati automaticamente in riferimento al lavoratore A, poiché si trova nello stesso processo (non è prevista alcuna copia).

2

In alcuni casi, è possibile parallelizzare automaticamente i loop usando Numba , sebbene funzioni solo con un piccolo sottoinsieme di Python:

from numba import njit, prange

@njit(parallel=True)
def prange_test(A):
    s = 0
    # Without "parallel=True" in the jit-decorator
    # the prange statement is equivalent to range
    for i in prange(A.shape[0]):
        s += A[i]
    return s

Sfortunatamente, sembra che Numba funzioni solo con array Numpy, ma non con altri oggetti Python. In teoria, potrebbe anche essere possibile compilare Python in C ++ e quindi parallelizzarlo automaticamente utilizzando il compilatore Intel C ++ , anche se non l'ho ancora provato.


2

È possibile utilizzare la jobliblibreria per eseguire calcoli paralleli e multiprocessing.

from joblib import Parallel, delayed

È possibile semplicemente creare una funzione fooche si desidera eseguire in parallelo e in base al seguente codice implementare l'elaborazione parallela:

output = Parallel(n_jobs=num_cores)(delayed(foo)(i) for i in input)

Dove è num_corespossibile ottenere dalla multiprocessinglibreria come segue:

import multiprocessing

num_cores = multiprocessing.cpu_count()

Se si dispone di una funzione con più di un argomento di input e si desidera semplicemente scorrere su uno degli argomenti di un elenco, è possibile utilizzare la partialfunzione dalla functoolslibreria come segue:

from joblib import Parallel, delayed
import multiprocessing
from functools import partial
def foo(arg1, arg2, arg3, arg4):
    '''
    body of the function
    '''
    return output
input = [11,32,44,55,23,0,100,...] # arbitrary list
num_cores = multiprocessing.cpu_count()
foo_ = partial(foo, arg2=arg2, arg3=arg3, arg4=arg4)
# arg1 is being fetched from input list
output = Parallel(n_jobs=num_cores)(delayed(foo_)(i) for i in input)

Puoi trovare una spiegazione completa del multiprocessing di Python e R con un paio di esempi qui .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.