You are on page 1of 42

Objetivos

Aprovechar al 100% los multicore modernos con Python Abrirse a la posibilidad de escalar a multi-nodo (clusters) Mantener la facilidad de desarrollo caracterstica de Python

Multiprocesamiento qu es?

El arte de dividir una tarea en varias subtareas paralelizables


Sumar dos arrays de nmeros Calcular promedios Multitud de otras tareas intensivas en cmputo

Tambin es realizar varias instancias independientes de una tarea en paralelo.

Ms transacciones por segundo, cuando son CPUbound

Multiprocesamiento qu no es?

Correr varias aplicaciones diferentes en un multicore Paralelismo de I/O

Ms transacciones por segundo, cuando son IObound

Formas de multiprocesamiento

Multiproceso

Correr varias instancias separadas del proceso, cada una con una parte del workload Esencialmente como multiproceso, pero:

Multithreading

Corren en el mismo espacio de memoria Se pueden crear ms threads y ms rpido que procesos

Multiproceso
Pipe

RAM P0 CPU0

RAM P1 CPU1

RAM Pn

No comparten nada

Excepto CPU si hay < n

CPUn

Slo se comunican por IPC


Pipes Sockets Archivos SHM

P0

P1
Pipe

Pn

Multithreading
Queue

Comparten memoria

Y CPU si hay < n

RAM

Se comunican rpidamente mediante estructuras compartidas

CPU0

CPU1

CPUn

Colas Buffers

Requieren sincronizacin

T0

T1
Queue

Tn

Mutexes Semforos

Bueno... hacelo en python

Multithreading en python

threading worker pools


Ejemplo
>>> ... ... ... ... >>> >>> >>> >>> >>> def stupid(): x = 0 while True: x = x + 1 from threading import Thread t1 = Thread(target=stupid) t2 = Thread(target=stupid) t1.start() t2.start() >>> ... ... ... >>> >>> >>> >>> >>> >>> >>>

Ejemplo
def geturl(url): from urllib import urlopen return urlopen(url).read() u1 = http://www.google.com.ar u2 = http://ar.livra.com from threading import Thread t1 = Thread(target=geturl, args=(u1,)) t2 = Thread(target=geturl, args=(u2,)) t1.start() t2.start()

WTF?
>>> ... ... ... ... >>> >>> >>> >>> >>> def stupid(): x = 0 while True: x = x + 1 from threading import Thread t1 = Thread(target=stupid) t2 = Thread(target=stupid) t1.start() t2.start()

WTF?
>>> ... ... ... ... >>> >>> >>> >>> >>> def stupid(): x = 0 while True: x = x + 1 from multiprocessing import Process t1 = Process(target=stupid) t2 = Process(target=stupid) t1.start() t2.start()

WTF?
>>> ... ... ... ... >>> >>> >>> >>> >>> def stupid(): x = 0 while True: x = x + 1 from multiprocessing import Process t1 = Process(target=stupid) t2 = Process(target=stupid) t1.start() t2.start()

WTF?

Por qu threading falla?

Compartir memoria es peligroso

Los threads pueden pisarse entre s y producir comportamiento no determinista Si ms de una instancia de CPython corriera al mismo tiempo, probablemente funcionara mal Global Interpreter Lock En python, no pueden correr dos threads a la vez

CPython no es thread-safe

Luego: GIL

GIL
Thread 1 Thread 2

Esperando el GIL Corriendo

WTF?

Para qu sirven los threads entonces?

Paralelizar I/O

Python libera el GIL cuando est esperando datos de la red o el disco Hay aplicaciones que son ms fciles de expresar como mltiples hilos de ejecucin que como un programa lineal

Simplificar conceptos

WTF?

Para qu sirven los threads entonces?

Paralelizar I/O

Python libera el GIL cuando est esperando datos de la red o el disco Hay aplicaciones que son ms fciles de expresar como mltiples hilos de ejecucin que como un programa lineal Si se conoce el truco correcto

Simplificar conceptos

Paralelizar cmputos

Opciones?

Otras implementacioens PyPy Jython Multiproceso fork multiprocessing XML-RPC MPI Multithreading Pyrex + with nogil

Opciones?

Otras implementacioens PyPy Jython Multiproceso fork multiprocessing XML-RPC MPI Multithreading Pyrex + with nogil

No siempre es aceptable Dificulta IPC Difcil de adoptar


Limitado a pequeas secciones de cmputo puro

Multiprocessing

Pros:

Conceptualmente equivalente a threading

Ya se vio un ejemplo de uso, prcticamente idntico a threading

Altamente portable Alto overhead de comunicacin interproceso Limitado a un nico nodo

Cons:

XML-RPC

Pros: Estndar Implementaciones heterogneas Escalable a mltiples nodos Cons: Difcil comunicacin interproceso Alto overhead (ms que multiprocessing) Esquema rgido No es transparente Afecta al diseo de la aplicacin No es un mero detalle de la implementacin

Pyrex + with nogil

Pros: Optimiza! Pyrex (simil-Python) genera cdigo C y luego cdigo nativo, optimizado por el compilador de C Permite crear estructuras por fuera de python, sin sus ataduras (GIL), e incluso con algo de esfuerzo utilizar las grandes libreras de C++ Por fuera parece python, por dentro es C

Cuando una seccin no necesita tocar estructuras de python, puede liberar el GIL (with nogil), permitiendo paralelismo y verdadero multithreading.

Pyrex + with nogil

Cons:

Necesita compilacin Hay que tener mucho cuidado con el uso de with nogil Como en C, errores de programacin pueden resultar en segmentation fault Algo que en Python rara vez se ve No es python puro

Pyrex

ejemplo
Ejemplo
_stupid.pyx
def stupid(): cdef int x with nogil: x = 0 while 1: x = x + 1 >>> >>> >>> >>> >>> >>>

Ejemplo
stupid.py
from _stupid import stupid from threading import Thread t1 = Thread(target=stupid) t2 = Thread(target=stupid) t1.start() t2.start()

Pyrex

ejemplo
Ejemplo
_stupid.pyx
def stupid(): cdef int x with nogil: x = 0 while 1: x = x + 1 >>> >>> >>> >>> >>> >>>

Ejemplo
stupid.py
from _stupid import stupid from threading import Thread t1 = Thread(target=stupid) t2 = Thread(target=stupid) t1.start() t2.start()

cdef int x le dice a pyrex que x no es un objeto python


Luego lo podemos manipular sin trabar el intrprete

Pyrex + with nogil

Secciones de clculo puro pueden liberar el GIL, habilitando verdadero multithreading Incluso podemos leer memoria compartida sin trabar el GIL
Incluso podemos escribir memoria compartida...

...si la sincronizacin se realiza explcitamente y aparte


Cuidado con los deadlocks por interaccin con el GIL

MPI

Pros

Altamente escalable Estndar

http://pympi.sourceforge.net/

PyMPI

Implementaciones heterogneas Orientado a supercomputadoras


Cons

Esquema de programacin complejo Difcil de aprender y programar

MPI

ejemplo (tomado de la pgina)


Ejemplo
barrier.py
import mpi data = open(foo%02d.data%mpi.rank).read() print Work on,mpi.rank DATA = data.upper() open(foo%02d.DATA%mpi.rank,w).write(DATA) mpi.barrier() if mpi.rank == 0: print DONE % mpirun -np 3 pyMPI barrier.py Work on 0 Work on 2 Work on 1 DONE

MPI

ejemplo (tomado de la pgina)


Ejemplo
barrier.py
import mpi data = open(foo%02d.data%mpi.rank).read() print Work on,mpi.rank DATA = data.upper() open(foo%02d.DATA%mpi.rank,w).write(DATA) mpi.barrier() if mpi.rank == 0: print DONE % mpirun -np 3 pyMPI barrier.py Work on 0 Work on 2 Work on 1 DONE

mpi.rank nos dice qu proceso somos


As podemos hacer que cada proceso haga su parte correspondiente

MPI

ejemplo (tomado de la pgina)


Ejemplo
barrier.py
import mpi data = open(foo%02d.data%mpi.rank).read() print Work on,mpi.rank DATA = data.upper() open(foo%02d.DATA%mpi.rank,w).write(DATA) mpi.barrier() if mpi.rank == 0: print DONE % mpirun -np 3 pyMPI barrier.py Work on 0 Work on 2 Work on 1 DONE

mpi.barrier hace que todos los procesos sincronicen en ese punto


Similar a fork/join en openMP

MPI

ejemplo (tomado de la pgina)


Ejemplo
barrier.py
import mpi data = open(foo%02d.data%mpi.rank).read() print Work on,mpi.rank DATA = data.upper() open(foo%02d.DATA%mpi.rank,w).write(DATA) mpi.barrier() if mpi.rank == 0: print DONE % mpirun -np 3 pyMPI barrier.py Work on 0 Work on 2 Work on 1 DONE

MPI necesita un ambiente especial pyMPI en vez de CPython, a travs de mpirun. Los clusters ya tienen esto, pero es difcil de configurar de entrecasa

Hbridos

Multiprocessing permite total paralelismo

Pero dificulta la comunicacin Pero GIL bloquea el paralelismo

Multithreading permite total comunicacin

Memoria compartida entre procesos

mmap permite compartir memoria entre procesos, no solo threads


Pero son slo buffers, no pueden contener objetos Lo que es bueno, porque el GIL existe para proteger operaciones thread-unsafe en objetos

Memoria compartida entre procesos

mmap permite compartir memoria entre procesos, no solo threads


Pero son slo buffers, no pueden contener objetos Lo que es bueno, porque el GIL existe para proteger operaciones thread-unsafe en objetos
Resistir la tentacin de usar pickle, struct, y marshal

Eliminara la ventaja de mmap Usar pyrex en cambio, y su habilidad de castear punteros y trabajar directamente en el buffer

Memoria compartida entre procesos

mmap permite

Lockless message queues Buffers compartidos Lookup tables compartidos Proxy Pool Cualquier otra estructura que se pueda trabajar directamente sobre el buffer

Memoria compartida entre procesos

mmap permite

Lockless message queues


locklessq.pyx

cdef class LocklessQueue: cdef object buf cdef void* vbuf cdef Py_ssize_t vbuflen cdef int _readp(self): return (<int*>self.vbuf)[0] cdef int _writep(self): return (<int*>self.vbuf)[1] def __init__(self, int capacity): self.buf = mmap(-1, sizeof(Msg)*capacity + sizeof(int)*2) PyObject_AsWriteBuffer(buf, &vbuf, &vbuflen)

Memoria compartida entre procesos

mmap permite

Lockless message queues


locklessq.pyx

cdef class LocklessQueue: ... def get(self): return MessageProxy(buf,self._readp()) def put(self): cdef object rv rv = MessageProxy(buf,self._writep()) (<int*>self.vbuf)[1] += sizeof(Msg) return rv def advance(self): (<int*>self.vbuf)[0] += sizeof(Msg)

Memoria compartida entre procesos

mmap permite

Lockless message queues


Funcionan muy bien cuando hay 1 productor 1 consumidor Ej: colas de tareas

Incluso se puede, con cuidado y pyrex, hacer versiones con ms de un productor o ms de un consumidor

Memoria compartida entre procesos

mmap permite

>>> >>> >>> >>> >>> ... ... ... >>>

Buffers compartidos
from multiprocessing import Process from mmap import mmap buf = mmap(-1,1000000) from random import random def randomize(buf,start,end): for i in xrange(start,end): buf[i] = chr(random()*255) map(Process.start, [Process(target=randomize, args=(buf,i*100000,(i+1)*100000)) for i in xrange(len(buf)/100000)])

Memoria compartida entre procesos

mmap permite

Buffers compartidos

En linux multiprocessing no tiene tanto overhead, este uso es anlogo a OpenMP y multiprocessing lo implementa bien. Cuidado con archivos y sockets

Memoria compartida entre procesos

mmap permite

Buffers compartidos

Ej: SharedPixelBuffer, un buffer de pixels compartidos donde todos pueden trabajar concurrentemente Ej: SharedIndex, un ndice comprimido que todos pueden consultar concurrentemente

Memoria compartida entre procesos

mmap permite

Proxy Pool

Un objeto que provee un acceso directo a otro dentro de un buffer compartido Puede encapsular un mutex si fuera necesario Ej: MessageProxy en LocklessQueue

Suele requerir el uso de pyrex para una eficiente implementacin

Memoria compartida entre procesos No es lo mismo que SHM?

S, pero...

Ms sencillo, directo, y viene como mdulo estndar Ms portable, SHM slo tiene buen soporte en sistemas POSIX considerablemente recientes (linux >= 2.6) http://semanchuk.com/philip/posix_ipc/ Mdulo de python que permite acceso a IPC Posix

posix_ipc

SHM / Semforos / Mutex / Colas de mensajes SHM se usa con MMAP! (facilita la transicin a SHM si fuera necesaria)

Conclusin

Ningn mtodo es la panacea universal Cada problema tiene su truco Cada truco su punto fuerte

Y su punto dbil Cython tambin Recordar que existe por una razn

embrace pyrex

El GIL no es el malo de la pelcula

You might also like