[Python] Decorated Concurrency - Python multiprocessing made really really easy

enrico franchi enrico.franchi a gmail.com
Lun 23 Maggio 2016 00:56:24 CEST


2016-05-21 13:24 GMT+01:00 alessandro medici <alexxandro.medici a gmail.com>:

>
>
> Il giorno 21 maggio 2016 12:51, enrico franchi <enrico.franchi a gmail.com>
> ha scritto:
>
>>
>>
>> 2016-05-20 13:59 GMT+01:00 alessandro medici <alexxandro.medici a gmail.com
>> >:
>>
>>>
>>> - "multiprocessing" implica (a meno di eccezioni notevoli) "pickle di
>>>> tutto"
>>>>
>>>
>>> ? cioè i dati vengono trasmessi via pickle e non via puntatori? Sure?
>>> O invece non ho capito cosa affermi? Sorry per la mia ignoranza, ma
>>> sono anziano e con i capelli MOLTO grigi.
>>>
>>
>> Non e' questione di ignoranza, basta rifletterci. Allora...
>> multi-*processing*.
>> Diciamo che e' sano assumere che siano piu' processi, ok? Quindi piu'
>> processi hanno spazi di indirizzamento separati (visto che sono appunto
>> processi multipli). Quindi... cosa se ne fa un processo di un puntatore a
>> uno spazio di indirizzamento a cui non ha accesso? Diciamo nulla? Ah... ma
>> c'e' la memoria condivisa... ecco, ora riflettiamo anche su questa cosa.
>> Quando tu scrivi
>>
>> foo = Bar()
>>
>> tu stai dicendo a Python di creare un oggetto. Bene. Non gli stai dicendo
>> dove crearlo. Il che vuole dire che non e' in memoria condivisa.
>> L'alternativa sarebbe immaginare che Python ficcasse *tutto quanto* in
>> memoria condivisa...
>>
>
> Già. Ho passato buona parte di ieri a guardarmi proprio il funzionamento
> del Manager
> dei processi. Ed ho lasciato a metà quello sul funzionamento di pickle.
>

Il mio consiglio e' di lasciare perdere quella parte di multiprocessing (a
dire il vero... io suggerirei di pensarci sopra 80 volte prima di fare roba
vera con multiprocessing -- poi ne parliamo --.

Non vuoi usare qualcosa che sia scomodo e bug prone come la memoria
condivisa, ma con le performance del message passing (anzi, sinceramente,
con le performance da fanalino di coda del message passing, visto che altre
implementazioni di concetti simili sono molto piu' efficienti).


> Il ragionamento che fai fila, però... Mettiamola così: mi piacerebbe che
> esistesse
> un'opzione tale che permettesse di definire una variabile in uno spazio
> condiviso ed
> accessibile direttamente dai singoli processi. Starebbe poi a me gestirla.
> E sicuramente
> in molti casi sarebbe più veloce che impacchettare e spacchettare in giro
> dati che, magari,
> servono solo in lettura. Poi, se voglio darmi da solo la zappa sui piedi,
> sono affari miei, in
> pura logica Python :-)
>

Si beh... come dire. Ora io capisco che a te piacerebbe questa feature. Io
posso dirti che *credi* che ti piacerebbe. Nel 2016 praticamente tutti
quelli che hanno architettura a memoria condivisa le stanno migrando, a
parte in determinati casi specifici (e.g., embedded, kernel, possibilmente
roba come pipeline audio e video, dove hai veramente tanti dati e copiarli
diventa fastidioso).

Detto questo, sarebbe una feature assolutamente complicatissima da
implementare e che costringerebbe a fare andare tutto come una lumaca e/o a
rompere completamente l'astrazione di Python.

1. Python non ha il concetto di "area di memoria". Costruisci un oggetto.
Python ci pensa. Qui tu dovresti in qualche modo dire a Python che un certo
oggetto deve essere in un'area di memoria condivisa.

2. Come lo facciamo? O lo facciamo in modo implicito (che e' ancora meno
banale) oppure inventiamo qualcosa tipo... boh

Foo.shared(...)

che ha la stessa semantica di un normale Foo(...) ma mette le cose in
memoria condivisa.

Bene: nell'esatto istante in cui tu scrivi quella cosa, vuole dire che un
bel po' di roba viene schiaffata in memoria condivisa. Per esempio
l'oggetto classe Foo deve finirci. Ma non solo... ci deve finire qualunque
oggetto nell'MRO di Foo. E stiamo cominciando a tirarci dietro parecchia
roba.

Pero' aspetta... magari io avevo da qualche parte una roba come:

class Foo(object):
    def __init__(self):
        self.bar = Bar()

Ecco, l'oggetto che stiamo assegnando a self.bar deve pure finire anche lui
in questa memoria condivisa. Lui, la sua classe e tutti i suoi antenati e
tutti gli oggetti che entrano a fare parte dell'istanza (e cosi'
ricorsivamente).

Ora... capisci che e' un bel po' di roba.

E capisci anche che praticamente *tutte* le operazioni su questi oggetti
devono essere protette da un lock. Perche'? Beh... perche' se siamo in
memoria condivisa vuole dire che piu' processi possono accedervi (o
meglio... se li ho messi in memoria condivisa e' perche' voglio accederci
da piu' processi).

Ecco... come ne usciamo? Abbiamo due soluzioni:
1. mettiamo un lock poco granulare sull'area di memoria
2. mettiamo lock granulari sugli oggetti

Bene... in un caso stiamo replicando, di fatto, i problemi del GIL anche a
multi-processing. Nell'altro invece stiamo facendo la cosa che tutti quelli
che hanno provato a togliere il GIL ci hanno detto essere lenta. In pratica
abbiamo perso in partenza. E nota che la cosa e' drammatica: ogni volta che
passi un oggetto ad una funzione (o che ritorni) devi agire sul reference
count di quell'oggetto. Che e' diventata magicamente una variabile contesa
fra piu' processi (e quindi o vai di lock oppure vai di CAS -- ma CAS
funziona bene quando ci sono tante letture e poche scritture, in questo
caso invece mi aspetto che siano bilanciate --). Insomma... inchiodi tipo
tutto.

Capisci cosa intendo quando ti dico che non vuoi veramente questa cosa? Tra
l'altro di fatto non conosco molti linguaggi di alto livello che ti
consentono una granularita' tale sull'uso della memoria. E la memoria
condivisa e' notoriamente una cattiva idea. E fa veramente a cazzotti con
il memory model di un linguaggio ad oggetti.



> Già e vediamo se ho capito, allora, e uso come esempio proprio il
> programmino nel sito che aggiorna il dizionario:
>
> Lancio da processo padre il processo figlio, al quale vengono passati,
> pickled via Manager, i dati
> dei parametri, poi quando il figlio arriva al punto dell'aggiornamento del
> dizionario il figlio
> chiama il Manager che pickla i dati di nuovo, li torno al padre (sotto GIL
> e quindi in lista d'attesa
> se vi sono altri processi che vogliono fare la stessa cosa) che aggiorna il
> dizionario e torna il controllo, sempre via Manager al figlio. Corretto?
>

Dimentica il GIL, in un certo senso. Cioe'... e' corretto se togli la
questione del GIL, che in tutto questo e' quasi non coinvolto. Non e'
esattamente cosi' per alcuni dettagli implementativi (ma non ho troppo
voglia di scendere cosi' in dettaglio). Diciamo che in tutto questo il GIL
non e' un problema; il "problema" se vuoi e' che appunto per "aggiornare
memoria" bisogna spedirsi indietro oggetti potenzialmente cicciotti. Poi si
trovano sempre scappatoie, eh... ma secondo me fra che non e' comodo e che
non e' veloce potrebbe bastare dimenticarsene.


> Se è così ora affronterei un multiprocesso con le idee molto più chiare
> sul cosa, quando, quanto
> passare al processo stesso e sull'architettura che darei al soft.
>

Ora  a me verrebbe da dire una cosa... se parli di "architettura", mi
aspetto che vuoi fare le cose per bene. Probabilmente mettere queste cose
in produzione.

Ecco... io tenderei a tenermi lontano da multiprocessing (e dai thread).
Diciamo che multiprocessing e' molto comodo se tipo devi buttare su uno
script con un po' di concorrenza o magari provare a macinare un po' di dati
in toolettino scientifico.

Per il resto, e' un oggetto che riserva molte sorprese, e non del tipo
divertente.

Se hai un problema I/O bound guardati cose come gevent, tornado, il vecchio
twisted o il nuovo asyncio. Se hai un problema CPU bound... boh, fai
offload a chi puoi. Numpy in certi casi. Se no a volte con un po' di
attenzione si possono fare cose belline con multiprocessing. Poi per dire
l'ultima volta che lo ho messo in produzione mi sono scritto a mano tutte
le primitive di comunicazione fra processi, perche' le implementazioni di
default di multiprocessing non facevano per me (semantica, + tirano su
thread, + sono lente... etc etc etc). Questo non vuole dire che per te non
andranno bene: probabilmente se sono ancora li, vuole dire che vanno bene
per la maggior parte delle persone.


-- 
.
..: -enrico-
-------------- parte successiva --------------
Un allegato HTML è stato rimosso...
URL: <http://lists.python.it/pipermail/python/attachments/20160522/5291fc51/attachment.html>


Maggiori informazioni sulla lista Python