Curso Gratuito en Ciencia de Datos y Aprendizaje Automático con Python




Guía rápida de rendimiento y programación paralela


Hay muchas opciones disponibles para mejorar el rendimiento de sus códigos Python. Lo primero que hay que determinar es qué limita su cálculo. Podría ser la velocidad de la CPU (poco probable), las limitaciones de la memoria ( computación fuera del núcleo ) o la velocidad de transferencia de datos (esperando a que lleguen los datos para su procesamiento). Si su código es Python puro, entonces puede intentar ejecutarlo con Pypy , que es una implementación alternativa de Python que emplea un compilador justo a tiempo . Si su código no experimenta una aceleración masiva con Pypy , entonces probablemente hay algo externo al código que lo está ralentizando (por ejemplo, acceso al disco o acceso a la red). Si Pypyno tiene ningún sentido porque está utilizando muchos módulos compilados que Pypy no admite, entonces hay muchas herramientas de diagnóstico disponibles.


Python tiene su propio perfil cProfile incorporado que puede invocar desde la línea de comandos como se muestra a continuación:


code

La salida del perfil se guarda en el archivo program.prof . Este archivo se puede visualizar en runnakerun para obtener una buena imagen gráfica de dónde pasa la mayor parte del tiempo el código. El administrador de tareas de su sistema operativo también puede proporcionar pistas a medida que se ejecuta su programa para ver cómo consume recursos. El perfil line_profiler de Robert Kern proporciona una manera excelente de ver cómo el código gasta su tiempo anotando cada línea del código por sus tiempos. En combinación con runnakerun , esto reduce los problemas al nivel de línea desde el nivel de función.


La situación más común es que su programa esté esperando datos del disco o de algún recurso de red ocupado. Esta es una situación común en la programación web y hay muchas herramientas bien establecidas para lidiar con esto. Python tiene un módulo de multiprocesamiento que forma parte de la biblioteca estándar. Esto facilita la generación de procesos de trabajadores infantiles que pueden interrumpirse y procesar individualmente pequeñas partes de un gran trabajo. Sin embargo, sigue siendo su responsabilidad como programador averiguar cómo distribuir los datos para su algoritmo. El uso de este módulo significa que los procesos individuales serán gestionados por el sistema operativo, que se encargará de equilibrar la carga.


code

Luego, ejecuta este programa en la terminal como se muestra a continuación:


code

Es de vital importancia que ejecute el programa desde la terminal de esta manera. No es posible hacer esto de forma interactiva desde dentro de Jupyter, digamos. Si observa el administrador de procesos en el sistema operativo, debería ver una serie de nuevos procesos de Python merodeando durante diez segundos. También debería ver el resultado de las declaraciones de impresión anteriores. Naturalmente, en una aplicación real, estaría asignando un trabajo significativo para cada uno de los trabajadores y averiguando cómo enviar piezas parcialmente terminadas entre trabajadores individuales. Hacer esto es complejo y fácil de equivocarse, por lo que Python 3 tiene los útiles concurrent.futures .


code

Debería ver algo como lo siguiente en la terminal. Tenga en cuenta que restringimos explícitamente el número de procesos a tres.


code

El módulo de futuros está construido sobre multiprocesamiento y facilita su uso para este tipo de tarea simple. Tenga en cuenta que también hay versiones de ambos que usan subprocesos en lugar de procesos mientras mantienen el mismo patrón de uso. El principal


La diferencia entre hilos y procesos es que los procesos tienen sus propios recursos compartimentados. La implementación de Python en lenguaje C (es decir, CPython) utiliza un bloqueo de intérprete global (GIL) que evita que los subprocesos se bloqueen en las estructuras de datos internas. Este es un mecanismo de bloqueo muy detallado en el que un hilo puede ejecutarse individualmente más rápido porque no tiene que realizar un seguimiento de toda la contabilidad involucrada en la ejecución de varios hilos simultáneamente. La desventaja es que no puede ejecutar varios subprocesos simultáneamente para acelerar ciertas tareas.


No hay un problema de bloqueo correspondiente con los procesos, pero estos son algo más lentos de iniciar porque cada proceso tiene que crear su propio espacio de trabajo privado para las estructuras de datos que pueden transferirse entre ellos. Sin embargo, cada proceso ciertamente puede ejecutarse de forma independiente y simultánea una vez que todo está configurado. Tenga en cuenta que ciertas implementaciones alternativas de Python como IronPython utilizan un diseño de subprocesos de grano fino en lugar de un enfoque GIL. Como comentario final, en los sistemas modernos con múltiples núcleos, podría ser que múltiples subprocesos realmente ralenticen las cosas porque el sistema operativo puede tener que cambiar subprocesos entre diferentes núcleos. Esto crea gastos generales adicionales en el mecanismo de cambio de hilo que, en última instancia, ralentizan las cosas.


Jupyter en sí tiene un marco de programación paralelo construido ( ipyparallel ) que es potente y fácil de usar. El primer paso es encender motores Jupyter separados en la terminal como se muestra a continuación:


code

Luego, en una ventana de Jupyter, puede obtener el cliente,


code

El cliente tiene una conexión con cada uno de los procesos que iniciamos antes de usar ipcluster . Para usar todos los motores, asignamos el objeto DirectView desde el cliente como se muestra a continuación:


code

Ahora, podemos aplicar funciones para cada uno de los motores. Por ejemplo, podemos obtener los identificadores de proceso usando la función os.getpid ,


code

Una vez que los motores están en funcionamiento, los datos se pueden distribuir a ellos mediante dispersión ,


code

Tenga en cuenta que el método de ejecución evalúa la cadena dada en cada motor. Ahora que los datos se han esparcido entre los motores activos, podemos hacer más cálculos sobre ellos,


code

En este ejemplo, sumamos las sublistas individuales a disponibles en cada uno de los motores. Podemos reunir los resultados individuales en una sola lista como se muestra a continuación:


code

Este es uno de los mecanismos más simples para distribuir el trabajo a los motores individuales y recolectar los resultados. A diferencia de los otros métodos que discutimos, puede hacerlo de forma iterativa, lo que facilita la experimentación con la forma en que desea distribuir y calcular con los datos. La documentación de Jupyter tiene muchos más ejemplos de estilos de programación paralelos que incluyen ejecutar los motores en recursos de la nube, clústeres de supercomputadoras y en distintos recursos informáticos en red. Aunque existen muchos otros paquetes de programación paralela especializados, Jupyter ofrece la mejor compensación entre generalidad y complejidad en todas las plataformas principales.