Me gustaría paralelizar mi programa Python para que pueda hacer uso de múltiples procesadores en la máquina en la que se ejecuta. Mi paralelización es muy simple, ya que todos los "hilos" paralelos del programa son independientes y escriben su salida en archivos separados. No necesito los hilos para intercambiar información, pero es imperativo que sepa cuándo terminan los hilos, ya que algunos pasos de mi canalización dependen de su salida.
La portabilidad es importante, ya que me gustaría que se ejecutara en cualquier versión de Python en Mac, Linux y Windows. Dadas estas limitaciones, ¿cuál es el módulo de Python más apropiado para implementar esto? Estoy tratando de decidir entre subprocesos, subprocesos y multiprocesamiento, que parecen proporcionar una funcionalidad relacionada.
Tiene alguna idea sobre esto? Me gustaría la solución más simple que sea portátil.
fuente
Respuestas:
multiprocessing
es un gran módulo de tipo navaja suiza. Es más general que los subprocesos, ya que incluso puede realizar cálculos remotos. Por tanto, este es el módulo que le sugiero que utilice.El
subprocess
módulo también le permitiría iniciar múltiples procesos, pero encontré que era menos conveniente de usar que el nuevo módulo de multiprocesamiento.Los subprocesos son notoriamente sutiles y, con CPython, a menudo está limitado a un núcleo, con ellos (aunque, como se indica en uno de los comentarios, el bloqueo de intérprete global (GIL) se puede lanzar en código C llamado desde código Python) .
Creo que la mayoría de las funciones de los tres módulos que cita se pueden utilizar de forma independiente de la plataforma. En el lado de la portabilidad, tenga en cuenta que
multiprocessing
solo viene de serie desde Python 2.6 (aunque existe una versión para algunas versiones anteriores de Python). ¡Pero es un gran módulo!fuente
Para mí, esto es bastante simple:
La opción de subproceso :
subprocess
es para ejecutar otros ejecutables --- es básicamente un contenedoros.fork()
yos.execve()
con algo de soporte para plomería opcional (configurando PIPE hacia y desde los subprocesos. Obviamente, podría otros mecanismos de comunicaciones entre procesos (IPC), como sockets, Posix o Memoria compartida SysV, pero estará limitado a las interfaces y canales IPC que sean compatibles con los programas que está llamando.Comúnmente, uno usa cualquier
subprocess
sincrónico --- simplemente llamando a alguna utilidad externa y leyendo su salida o esperando su finalización (tal vez leyendo sus resultados de un archivo temporal, o después de que los haya publicado en alguna base de datos).Sin embargo, uno puede generar cientos de subprocesos y sondearlos. Mi clase de utilidad favorita personal hace exactamente eso. La mayor desventaja del
subprocess
módulo es que el soporte de E / S generalmente se bloquea. Hay un borrador PEP-3145 para arreglar eso en alguna versión futura de Python 3.xy un asyncproc alternativo (Advertencia que conduce directamente a la descarga, no a ningún tipo de documentación ni README). También descubrí que es relativamente fácil simplemente importarfcntl
y manipular losPopen
descriptores de archivos PIPE directamente, aunque no sé si esto es portátil para plataformas que no son UNIX.(Actualización: 7 de agosto de 2019: soporte de Python 3 para subprocesos ayncio : subprocesos asyncio )
subprocess
casi no tiene soporte de manejo de eventos ... aunque puede usar elsignal
módulo y señales simples de UNIX / Linux de la vieja escuela --- matando sus procesos suavemente, por así decirlo.La opción de multiprocesamiento :
multiprocessing
es para ejecutar funciones dentro de su código (Python) existente con soporte para comunicaciones más flexibles entre esta familia de procesos. En particular, es mejor construir sumultiprocessing
IPC alrededor de losQueue
objetos del módulo cuando sea posible, pero también puede usarEvent
objetos y varias otras características (algunas de las cuales, presumiblemente, están construidas alrededor delmmap
soporte en las plataformas donde ese soporte es suficiente).El
multiprocessing
módulo de Python está destinado a proporcionar interfaces y características que son muy similares a lasthreading
que, al mismo tiempo, permiten a CPython escalar su procesamiento entre múltiples CPU / núcleos a pesar del GIL (bloqueo de intérprete global). Aprovecha todo el esfuerzo de coherencia y bloqueo SMP detallado que realizaron los desarrolladores del kernel de su sistema operativo.La opción de enhebrado :
threading
es para una gama bastante estrecha de aplicaciones que están vinculadas a E / S (no es necesario escalar a través de múltiples núcleos de CPU) y que se benefician de la latencia extremadamente baja y la sobrecarga de conmutación de la conmutación de subprocesos (con memoria de núcleo compartida) frente al proceso / cambio de contexto. En Linux, este es casi el conjunto vacío (los tiempos de cambio de proceso de Linux están extremadamente cerca de sus cambios de hilo).threading
sufre de dos grandes desventajas en Python .Uno, por supuesto, es específico de la implementación, que afecta principalmente a CPython. Ese es el GIL. En su mayor parte, la mayoría de los programas CPython no se beneficiarán de la disponibilidad de más de dos CPU (núcleos) y, a menudo, el rendimiento se verá afectado por la contención de bloqueo de GIL.
El problema más importante, que no es específico de la implementación, es que los subprocesos comparten la misma memoria, controladores de señales, descriptores de archivos y otros recursos del sistema operativo. Por lo tanto, el programador debe tener mucho cuidado con el bloqueo de objetos, el manejo de excepciones y otros aspectos de su código que son sutiles y que pueden matar, detener o bloquear todo el proceso (conjunto de subprocesos).
En comparación, el
multiprocessing
modelo le da a cada proceso su propia memoria, descriptores de archivo, etc. Una falla o una excepción no controlada en cualquiera de ellos solo matará ese recurso y manejar de manera robusta la desaparición de un proceso secundario o hermano puede ser considerablemente más fácil que depurar, aislar y solucionar o solucionar problemas similares en subprocesos.threading
con los principales sistemas Python, como NumPy , puede sufrir considerablemente menos contención de GIL que la mayoría de su propio código Python. Eso es porque han sido diseñados específicamente para hacerlo; las partes nativas / binarias de NumPy, por ejemplo, lanzará el GIL cuando sea seguro).La opción retorcida :
También vale la pena señalar que Twisted ofrece otra alternativa que es elegante y muy difícil de entender . Básicamente, a riesgo de simplificar demasiado hasta el punto en que los fanáticos de Twisted pueden asaltar mi hogar con horquillas y antorchas, Twisted ofrece multitarea cooperativa impulsada por eventos dentro de cualquier proceso (único).
Para comprender cómo esto es posible, uno debe leer acerca de las características de
select()
(que se pueden construir alrededor de select () o poll () o llamadas al sistema de sistema operativo similares). Básicamente, todo está impulsado por la capacidad de realizar una solicitud del sistema operativo para que se suspenda en espera de cualquier actividad en una lista de descriptores de archivos o algún tiempo de espera.El despertar de cada una de estas llamadas a
select()
es un evento, ya sea uno que involucre entrada disponible (legible) en cierto número de sockets o descriptores de archivo, o espacio de almacenamiento disponible en algunos otros descriptores o sockets (escribibles), algunas condiciones excepcionales (TCP paquetes PUSH fuera de banda, por ejemplo), o un TIMEOUT.Por lo tanto, el modelo de programación Twisted se basa en el manejo de estos eventos y luego en el controlador "principal" resultante, lo que le permite enviar los eventos a sus controladores.
Personalmente, creo que el nombre Twisted evoca el modelo de programación ... ya que su enfoque del problema debe ser, en cierto sentido, "retorcido" de adentro hacia afuera. En lugar de concebir su programa como una serie de operaciones sobre datos de entrada y salidas o resultados, está escribiendo su programa como un servicio o demonio y definiendo cómo reacciona a varios eventos. (De hecho, el núcleo "bucle principal" de un programa Twisted es (¿normalmente? ¿Siempre?) A
reactor()
).Los principales desafíos para el uso de Twisted implican darle vueltas al modelo impulsado por eventos y también evitar el uso de bibliotecas de clases o kits de herramientas que no estén escritos para cooperar dentro del marco Twisted. Esta es la razón por la que Twisted proporciona sus propios módulos para el manejo de protocolos SSH, para curses y sus propias funciones de subproceso / Popen, y muchos otros módulos y manejadores de protocolos que, a primera vista, parecerían duplicar cosas en las bibliotecas estándar de Python.
Creo que es útil comprender Twisted a un nivel conceptual, incluso si nunca tienes la intención de usarlo. Puede brindar información sobre el rendimiento, la contención y el manejo de eventos en su subproceso, multiprocesamiento e incluso manejo de subprocesos, así como cualquier procesamiento distribuido que realice.
( Nota: Las nuevas versiones de Python 3.x están incluidos asyncio (E / S asíncrona) presenta como asíncrono def , el @ async.coroutine decorador, y el aguardan palabra clave y el rendimiento del futuro apoyo todos estos son más o menos similares a. Retorcido desde una perspectiva de proceso (cooperativa multitarea). (Para conocer el estado actual del soporte Twisted para Python 3, consulte: https://twistedmatrix.com/documents/current/core/howto/python3.html )
La opción distribuida :
Otro ámbito del procesamiento sobre el que no ha preguntado, pero que vale la pena considerar, es el del procesamiento distribuido . Existen muchas herramientas y marcos de Python para el procesamiento distribuido y el cálculo paralelo. Personalmente, creo que el más fácil de usar es el que menos se considera que está en ese espacio.
Es casi trivial construir un procesamiento distribuido alrededor de Redis . Todo el almacén de claves se puede usar para almacenar unidades de trabajo y resultados, las LIST de Redis se pueden usar como
Queue()
un objeto similar y el soporte PUB / SUB se puede usar para unEvent
manejo similar. Puede aplicar hash a sus claves y utilizar valores, replicados en un clúster suelto de instancias de Redis, para almacenar la topología y las asignaciones de tokens de hash para proporcionar hash y conmutación por error consistentes para escalar más allá de la capacidad de cualquier instancia única para coordinar a sus trabajadores y ordenación de datos (encurtidos, JSON, BSON o YAML) entre ellos.Por supuesto, a medida que comienza a construir una solución a mayor escala y más sofisticada alrededor de Redis, está reimplementando muchas de las características que ya se han resuelto con Celery , Apache Spark y Hadoop , Zookeeper , etcd , Cassandra , etc. Todos ellos tienen módulos para que Python acceda a sus servicios.
[Actualización: un par de recursos para considerar si está considerando Python para uso intensivo de computación en sistemas distribuidos: IPython Parallel y PySpark . Si bien estos son sistemas informáticos distribuidos de propósito general, son subsistemas de ciencia y análisis de datos particularmente accesibles y populares].
Conclusión
Allí tiene la gama de alternativas de procesamiento para Python, desde un solo subproceso, con simples llamadas sincrónicas a subprocesos, grupos de subprocesos sondeados, subprocesos y multiprocesamiento, multitarea cooperativa impulsada por eventos y procesamiento distribuido.
fuente
En un caso similar, opté por procesos separados y el poquito de comunicación necesaria a través del enchufe de red. Es altamente portátil y bastante simple de hacer usando Python, pero probablemente no sea más simple (en mi caso también tenía otra restricción: la comunicación con otros procesos escritos en C ++).
En su caso, probablemente optaría por el multiproceso, ya que los subprocesos de Python, al menos cuando se usa CPython, no son subprocesos reales. Bueno, son subprocesos nativos del sistema, pero los módulos C llamados desde Python pueden o no liberar el GIL y permitir que otros subprocesos se ejecuten al llamar al código de bloqueo.
fuente
Para utilizar varios procesadores en CPython, su única opción es el
multiprocessing
módulo. CPython mantiene un bloqueo en sus componentes internos (el GIL ) que evita que los subprocesos en otros cpus funcionen en paralelo. Elmultiprocessing
módulo crea nuevos procesos (comosubprocess
) y gestiona la comunicación entre ellos.fuente
Desplácese y deje que unix haga su trabajo:
use iterpipes para envolver el subproceso y luego:
Del sitio de Ted Ziuba
INPUTS_FROM_YOU | xargs -n1 -0 -P NUM ./process #NUM procesos paralelos
O
Gnu Parallel también servirá
Pasa el rato con GIL mientras envía a los chicos de la trastienda a hacer su trabajo multinúcleo.
fuente