Condivisione di array Numpy di grandi dimensioni e di sola lettura tra processi multiprocessing


88

Ho un SciPy Array (Matrix) da 60 GB che devo condividere tra 5+ multiprocessing Processoggetti. Ho visto numpy-sharedmem e ho letto questa discussione nell'elenco SciPy. Sembra che ci siano due approaches-- numpy-sharedmeme utilizzando una multiprocessing.RawArray()e la mappatura NumPy dtypes a ctypes. Ora, numpy-sharedmemsembra essere la strada da percorrere, ma devo ancora vedere un buon esempio di riferimento. Non ho bisogno di alcun tipo di blocco, poiché l'array (in realtà una matrice) sarà di sola lettura. Ora, a causa delle sue dimensioni, vorrei evitare una copia. E suona come il metodo corretto è quello di creare l' unica copia della matrice come un sharedmemarray, e poi passarlo agli Processoggetti? Un paio di domande specifiche:

  1. Qual è il modo migliore per passare effettivamente gli handle di sharedmem ai sottomessi Process()? Ho bisogno di una coda solo per passare un array? Sarebbe meglio una pipa? Posso semplicemente passarlo come argomento Process()all'init della sottoclasse (dove presumo sia in salamoia)?

  2. Nella discussione che ho collegato sopra, si parla di numpy-sharedmemnon essere sicuri a 64 bit? Sto sicuramente usando alcune strutture che non sono indirizzabili a 32 bit.

  3. Ci sono compromessi RawArray()nell'approccio? Più lento, più forte?

  4. Ho bisogno di una mappatura da ctype a dtype per il metodo numpy-sharedmem?

  5. Qualcuno ha un esempio di codice OpenSource che lo fa? Sono un dotto molto pratico ed è difficile farlo funzionare senza alcun tipo di buon esempio da guardare.

Se ci sono informazioni aggiuntive che posso fornire per chiarire questo aspetto ad altri, ti preghiamo di commentare e aggiungerò. Grazie!

Questo deve essere eseguito su Ubuntu Linux e forse Mac OS, ma la portabilità non è una grande preoccupazione.


1
Se i diversi processi scriveranno su quell'array, aspettati multiprocessingdi fare una copia dell'intera cosa per ogni processo.
tiago

3
@tiago: "Non ho bisogno di alcun tipo di blocco, poiché l'array (in realtà una matrice) sarà di sola lettura"
Dr. Jan-Philip Gehrcke

1
@tiago: inoltre, multiprocessing non sta facendo una copia fintanto che non viene esplicitamente detto a (tramite argomenti a target_function). Il sistema operativo copierà parti della memoria del genitore nello spazio di memoria del bambino solo dopo la modifica.
Dr. Jan-Philip Gehrcke


Ho fatto alcune domande su questo prima. La mia soluzione può essere trovata qui: github.com/david-hoffman/peaks/blob/… (scusate il codice è un disastro).
David Hoffman

Risposte:


30

@Velimir Mlaker ha dato un'ottima risposta. Ho pensato di poter aggiungere qualche commento e un piccolo esempio.

(Non sono riuscito a trovare molta documentazione su sharedmem: questi sono i risultati dei miei esperimenti.)

  1. È necessario passare gli handle quando il sottoprocesso viene avviato o dopo che è stato avviato? Se è solo il primo, puoi semplicemente usare gli argomenti targete argsperProcess . Questo è potenzialmente migliore rispetto all'utilizzo di una variabile globale.
  2. Dalla pagina di discussione che hai collegato, sembra che il supporto per Linux a 64 bit sia stato aggiunto a sharedmem qualche tempo fa, quindi potrebbe essere un non problema.
  3. Non so niente di questo.
  4. No. Fare riferimento all'esempio seguente.

Esempio

#!/usr/bin/env python
from multiprocessing import Process
import sharedmem
import numpy

def do_work(data, start):
    data[start] = 0;

def split_work(num):
    n = 20
    width  = n/num
    shared = sharedmem.empty(n)
    shared[:] = numpy.random.rand(1, n)[0]
    print "values are %s" % shared

    processes = [Process(target=do_work, args=(shared, i*width)) for i in xrange(num)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()

    print "values are %s" % shared
    print "type is %s" % type(shared[0])

if __name__ == '__main__':
    split_work(4)

Produzione

values are [ 0.81397784  0.59667692  0.10761908  0.6736734   0.46349645  0.98340718
  0.44056863  0.10701816  0.67167752  0.29158274  0.22242552  0.14273156
  0.34912309  0.43812636  0.58484507  0.81697513  0.57758441  0.4284959
  0.7292129   0.06063283]
values are [ 0.          0.59667692  0.10761908  0.6736734   0.46349645  0.
  0.44056863  0.10701816  0.67167752  0.29158274  0.          0.14273156
  0.34912309  0.43812636  0.58484507  0.          0.57758441  0.4284959
  0.7292129   0.06063283]
type is <type 'numpy.float64'>

Questa domanda correlata potrebbe essere utile.


37

Se sei su Linux (o qualsiasi sistema compatibile con POSIX), puoi definire questo array come una variabile globale. multiprocessingsta usando fork()su Linux quando avvia un nuovo processo figlio. Un processo figlio appena generato condivide automaticamente la memoria con il suo genitore fintanto che non la modifica ( copia su scrittura meccanismo di ).

Dal momento che stai dicendo "Non ho bisogno di alcun tipo di blocco, poiché l'array (in realtà una matrice) sarà di sola lettura", approfittare di questo comportamento sarebbe un approccio molto semplice e tuttavia estremamente efficiente: tutti i processi figli accederanno gli stessi dati nella memoria fisica durante la lettura di questo grande array numpy.

Non consegnare la matrice per il Process()costruttore, questo istruirà multiprocessingper picklei dati al bambino, che sarebbe estremamente inefficiente o impossibile nel tuo caso. Su Linux, subito dopo fork()il bambino c'è una copia esatta del genitore che usa la stessa memoria fisica, quindi tutto ciò che devi fare è assicurarti che la variabile Python 'contenente' la matrice sia accessibile dall'interno della targetfunzione che consegniProcess() . Questo è tipicamente possibile ottenere con una variabile "globale".

Codice di esempio:

from multiprocessing import Process
from numpy import random


global_array = random.random(10**4)


def child():
    print sum(global_array)


def main():
    processes = [Process(target=child) for _ in xrange(10)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()


if __name__ == "__main__":
    main()

Su Windows, che non supporta fork(), multiprocessingutilizza la chiamata API win32 CreateProcess. Crea un processo completamente nuovo da un dato eseguibile. Ecco perché su Windows è necessario raccogliere i dati al bambino se sono necessari dati che sono stati creati durante il runtime del genitore.


3
Copy-on-write copierà la pagina contenente il contatore di riferimento (quindi ogni pitone biforcuto avrà il proprio contatore di riferimento) ma non copierà l'intero array di dati.
robince

1
Vorrei aggiungere che ho avuto più successo con le variabili a livello di modulo che con le variabili globali ... cioè aggiungere la variabile a un modulo nell'ambito globale prima del fork
robince

5
Un avvertimento per le persone che si imbattono in questa domanda / risposta: se ti capita di utilizzare Numpy collegato a OpenBLAS per la sua operazione multithread, assicurati di disabilitare il suo multithreading (esporta OPENBLAS_NUM_THREADS = 1) durante l'utilizzo multiprocessingo i processi figli potrebbero finire per bloccarsi ( tipicamente utilizzando 1 / n di un processore anziché n processori) durante l'esecuzione di operazioni di algebra lineare su una matrice / matrice globale condivisa. Il noto conflitto multithread con OpenBLAS sembra estendersi a Pythonmultiprocessing
Dologan

1
Qualcuno può spiegare perché Python non userebbe semplicemente il sistema operativo forkper passare i parametri forniti Process, invece di serializzarli? Cioè, non potrebbe forkessere applicato al processo genitore appena prima di child essere chiamato, in modo che il valore del parametro sia ancora disponibile dal sistema operativo? Sembrerebbe essere più efficiente della serializzazione?
max

2
Siamo tutti consapevoli che fork()non è disponibile su Windows, è stato affermato nella mia risposta e più volte nei commenti. So che questa è stata la sua domanda iniziale, e io ho risposto quattro commenti sopra questo : "il compromesso è quello di utilizzare lo stesso metodo di trasferimento dei parametri su entrambe le piattaforme per impostazione predefinita, per una migliore manutenibilità e per assicurare la parità di comportamento.". Entrambi i modi hanno i loro vantaggi e svantaggi, motivo per cui in Python 3 c'è una maggiore flessibilità per l'utente nella scelta del metodo. Questa discussione non è produttiva senza parlare di dettagli, cosa che non dovremmo fare qui.
Dr. Jan-Philip Gehrcke

24

Potresti essere interessato a un minuscolo pezzo di codice che ho scritto: github.com/vmlaker/benchmark-sharedmem

L'unico file di interesse è main.py. È un benchmark di numpy-sharedmem : il codice passa semplicemente gli array (o numpyo sharedmem) ai processi generati, tramite Pipe. I lavoratori si limitano a richiamare sum()i dati. A me interessava solo confrontare i tempi di comunicazione dei dati tra le due implementazioni.

Ho anche scritto un altro codice più complesso: github.com/vmlaker/sherlock .

Qui utilizzo il modulo numpy-sharedmem per l'elaborazione delle immagini in tempo reale con OpenCV: le immagini sono array NumPy, come da cv2API più recente di OpenCV . Le immagini, in realtà i relativi riferimenti, sono condivise tra i processi tramite l'oggetto dizionario creato da multiprocessing.Manager(al contrario di utilizzare Queue o Pipe.) Sto ottenendo grandi miglioramenti delle prestazioni rispetto all'utilizzo di semplici array NumPy.

Pipe vs. Queue :

Nella mia esperienza, IPC con Pipe è più veloce di Queue. E questo ha senso, poiché Queue aggiunge il blocco per renderlo sicuro per più produttori / consumatori. Pipe no. Ma se hai solo due processi che parlano avanti e indietro, è sicuro usare Pipe o, come leggono i documenti:

... non vi è alcun rischio di corruzione da processi che utilizzano contemporaneamente estremità diverse del tubo.

sharedmemsicurezza :

Il problema principale con il sharedmemmodulo è la possibilità di perdita di memoria all'uscita dal programma sgradevole. Questo è descritto in una lunga discussione qui . Sebbene il 10 aprile 2011 Sturla abbia menzionato una correzione alla perdita di memoria, da allora ho ancora riscontrato perdite, utilizzando entrambi i repository, quello di Sturla Molden su GitHub ( github.com/sturlamolden/sharedmem-numpy ) e Chris Lee-Messer su Bitbucket ( bitbucket.org/cleemesser/numpy-sharedmem ).


Grazie, molto molto istruttivo. Tuttavia, la perdita di memoria sharedmemsuona come un grosso problema. Qualche pista per risolverlo?
Will

1
Oltre a notare le perdite, non l'ho cercato nel codice. Ho aggiunto alla mia risposta, sotto "sharedmem safety" sopra, i custodi dei due repository open source del sharedmemmodulo, per riferimento.
Velimir Mlaker

14

Se il tuo array è così grande puoi usare numpy.memmap. Ad esempio, se hai un array memorizzato nel disco, 'test.array'puoi usare processi simultanei per accedere ai dati in esso anche in modalità "scrittura", ma il tuo caso è più semplice poiché hai solo bisogno della modalità "lettura".

Creazione dell'array:

a = np.memmap('test.array', dtype='float32', mode='w+', shape=(100000,1000))

È quindi possibile riempire questo array nello stesso modo in cui si fa con un normale array. Per esempio:

a[:10,:100]=1.
a[10:,100:]=2.

I dati vengono memorizzati su disco quando si elimina la variabile a.

Successivamente è possibile utilizzare più processi che accederanno ai dati in formato test.array :

# read-only mode
b = np.memmap('test.array', dtype='float32', mode='r', shape=(100000,1000))

# read and writing mode
c = np.memmap('test.array', dtype='float32', mode='r+', shape=(100000,1000))

Risposte correlate:


3

Potresti anche trovare utile dare un'occhiata alla documentazione di pyro come se fosse possibile partizionare il tuo compito in modo appropriato, potresti usarlo per eseguire diverse sezioni su macchine diverse così come su diversi core nella stessa macchina.


0

Perché non utilizzare il multithreading? Le risorse del processo principale possono essere condivise dai suoi thread in modo nativo, quindi il multithreading è ovviamente un modo migliore per condividere oggetti di proprietà del processo principale.

Se ti preoccupi del meccanismo GIL di python, forse puoi ricorrere a nogilof numba.

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.