¿Qué es Cython? Python a la velocidad de C

Python tiene la reputación de ser uno de los lenguajes de programación más convenientes, mejor equipados y francamente útiles. ¿Velocidad de ejecución? No tanto.

Ingresa Cython. El lenguaje Cython es un superconjunto de Python que se compila en C, lo que produce mejoras en el rendimiento que pueden variar desde un pequeño porcentaje hasta varios órdenes de magnitud, según la tarea en cuestión. Para el trabajo que está limitado por los tipos de objetos nativos de Python, las aceleraciones no serán grandes. Pero para las operaciones numéricas, o cualquier operación que no involucre los componentes internos de Python, las ganancias pueden ser masivas. 

Con Cython, puede eludir muchas de las limitaciones nativas de Python o trascenderlas por completo, sin tener que renunciar a la facilidad y conveniencia de Python. En este artículo, repasaremos los conceptos básicos detrás de Cython y crearemos una aplicación Python simple que usa Cython para acelerar una de sus funciones.

Video relacionado: Uso de Cython para acelerar Python

Compilar Python en C

El código Python puede realizar llamadas directamente a módulos C. Esos módulos C pueden ser bibliotecas C genéricas o bibliotecas creadas específicamente para trabajar con Python. Cython genera el segundo tipo de módulo: bibliotecas C que se comunican con los componentes internos de Python y que se pueden incluir con el código Python existente.

El código Cython se parece mucho al código Python, por diseño. Si alimenta el compilador Cython con un programa Python (ambos son compatibles con Python 2.xy Python 3.x), Cython lo aceptará tal cual, pero ninguna de las aceleraciones nativas de Cython entrará en juego. Pero si decora el código Python con anotaciones de tipo en la sintaxis especial de Cython, Cython podrá sustituir los equivalentes rápidos de C por objetos lentos de Python.

Tenga en cuenta que el enfoque de Cython es  incremental . Eso significa que un desarrollador puede comenzar con una  aplicación Python existente y acelerarla haciendo cambios puntuales en el código, en lugar de reescribir toda la aplicación desde cero.

Este enfoque encaja con la naturaleza de los problemas de rendimiento del software en general. En la mayoría de los programas, la gran mayoría del código de uso intensivo de la CPU se concentra en algunos puntos calientes, una versión del principio de Pareto, también conocida como la regla “80/20”. Por lo tanto, la mayor parte del código en una aplicación Python no necesita estar optimizado para el rendimiento, solo algunas piezas críticas. Puede traducir gradualmente esos puntos calientes a Cython y así obtener las ganancias de rendimiento que necesita donde más importa. El resto del programa puede permanecer en Python para comodidad de los desarrolladores.

Cómo usar Cython

Considere el siguiente código, tomado de la documentación de Cython:

def f (x):

    devolver x ** 2-x

def integra_f (a, b, N):

    s = 0

    dx = (ba) / N

    para i en el rango (N):

        s + = f (a + i * dx)

    devolver s * dx

Este es un ejemplo de juguete, una implementación no muy eficiente de una función integral. Como código Python puro, es lento, porque Python debe realizar conversiones entre tipos numéricos nativos de la máquina y sus propios tipos de objetos internos.

Ahora considere la versión Cython del mismo código, con las adiciones de Cython subrayadas:

 cdef doble f (doble x):

    devolver x ** 2-x

def integra_f (doble a, doble b, int N):

    cdef int i

    cdef doble s, x, dx

    s = 0

    dx = (ba) / N

    para i en el rango (N):

        s + = f (a + i * dx)

    devolver s * dx

Si declaramos explícitamente los tipos de variables, tanto para los parámetros de la función y las variables utilizadas en el cuerpo de la función ( double, int, etc.), Cython se traducirá todo esto en C. También podemos usar la cdefpalabra clave para definir las funciones que son implementado principalmente en C para mayor velocidad, aunque esas funciones solo pueden ser llamadas por otras funciones de Cython y no por scripts de Python. (En el ejemplo anterior, solo integrate_fotro script de Python puede llamarlo).

Tenga en cuenta lo poco que ha cambiado nuestro código real  . Todo lo que hemos hecho es agregar declaraciones de tipo al código existente para obtener un aumento significativo del rendimiento.

Ventajas de Cython

Además de poder acelerar el código que ya ha escrito, Cython otorga varias otras ventajas:

Trabajar con bibliotecas C externas puede ser más rápido

Los paquetes de Python como NumPy envuelven las bibliotecas de C en las interfaces de Python para que sea más fácil trabajar con ellas. Sin embargo, ir y venir entre Python y C a través de esos contenedores puede ralentizar las cosas. Cython le permite hablar con las bibliotecas subyacentes directamente, sin Python en el camino. (Las bibliotecas de C ++ también son compatibles).

Puede usar la administración de memoria tanto en C como en Python

Si usa objetos de Python, se administran en memoria y se recolectan basura de la misma manera que en Python normal. Pero si desea crear y administrar sus propias estructuras de nivel C y usar malloc/ freepara trabajar con ellas, puede hacerlo. Solo recuerda limpiar después de ti mismo.

Puede optar por la seguridad o la velocidad según sea necesario 

Cython realiza automáticamente comprobaciones en tiempo de ejecución para detectar problemas comunes que surgen en C, como el acceso fuera de límites a una matriz, mediante decoradores y directivas del compilador (por ejemplo, @boundscheck(False)). En consecuencia, el código C generado por Cython es mucho más seguro de forma predeterminada que el código C elaborado a mano, aunque potencialmente a costa del rendimiento en bruto.

Si está seguro de que no necesitará esas comprobaciones en tiempo de ejecución, puede desactivarlas para obtener ganancias de velocidad adicionales, ya sea en todo el módulo o solo en funciones seleccionadas.

Cython también le permite acceder de forma nativa a las estructuras de Python que utilizan el protocolo de búfer para acceder directamente a los datos almacenados en la memoria (sin copia intermedia). Las vistas de memoria de Cython le permiten trabajar con esas estructuras a alta velocidad y con el nivel de seguridad apropiado para la tarea. Por ejemplo, los datos sin procesar subyacentes a una cadena de Python se pueden leer de esta manera (rápido) sin tener que pasar por el tiempo de ejecución de Python (lento).

El código Cython C puede beneficiarse de la publicación de GIL

El bloqueo de intérprete global de Python, o GIL, sincroniza los hilos dentro del intérprete, protegiendo el acceso a los objetos de Python y gestionando la contención de recursos. Pero el GIL ha sido ampliamente criticado como un obstáculo para un Python de mejor rendimiento, especialmente en sistemas multinúcleo.

Si tiene una sección de código que no hace referencias a los objetos de Python y realiza una operación de ejecución prolongada, puede marcarla con la  with nogil:directiva para permitir que se ejecute sin GIL. Esto libera al intérprete de Python para hacer otras cosas y permite que el código de Cython haga uso de múltiples núcleos (con trabajo adicional).

Cython puede usar la sintaxis de sugerencias de tipo Python 

Python tiene una sintaxis de sugerencia de tipo que es utilizada principalmente por linters y verificadores de código, en lugar del intérprete CPython. Cython tiene su propia sintaxis personalizada para decoraciones de código, pero con las revisiones recientes de Cython, puede usar la sintaxis de sugerencias de tipo de Python para proporcionar sugerencias de tipo básicas a Cython también. 

Cython se puede utilizar para ocultar el código sensible de Python

Los módulos de Python son trivialmente fáciles de descompilar e inspeccionar, pero los binarios compilados no lo son. Al distribuir una aplicación de Python a los usuarios finales, si desea proteger algunos de sus módulos de espías casuales, puede hacerlo compilándolos con Cython. Sin embargo, tenga en cuenta que este es un efecto secundario de las capacidades de Cython, no una de sus funciones previstas.

Limitaciones de Cython

Tenga en cuenta que Cython no es una varita mágica. No convierte automáticamente cada instancia de código Python en código C ultrarrápido. Para aprovechar Cython al máximo, debe usarlo con prudencia y comprender sus limitaciones:

Poca aceleración para el código Python convencional

Cuando Cython encuentra código Python que no puede traducir completamente a C, transforma ese código en una serie de llamadas C a los componentes internos de Python. Esto equivale a sacar al intérprete de Python del ciclo de ejecución, lo que le da al código una modesta aceleración del 15 al 20 por ciento por defecto. Tenga en cuenta que este es el mejor de los casos; en algunas situaciones, es posible que no vea ninguna mejora en el rendimiento o incluso una degradación del rendimiento.

Poca aceleración para estructuras de datos nativas de Python

Python proporciona una gran cantidad de estructuras de datos: cadenas, listas, tuplas, diccionarios, etc. Son muy convenientes para los desarrolladores y vienen con su propia administración automática de memoria. Pero son más lentos que el C.

Cython le permite continuar usando todas las estructuras de datos de Python, aunque sin mucha aceleración. Esto se debe, nuevamente, a que Cython simplemente llama a las API de C en el tiempo de ejecución de Python que crean y manipulan esos objetos. Por lo tanto, las estructuras de datos de Python se comportan de manera muy similar al código Python optimizado para Cython en general: a veces obtienes un impulso, pero solo un poco. Para obtener mejores resultados, use variables y estructuras C. La buena noticia es que Cython facilita el trabajo con ellos.

El código Cython se ejecuta más rápido cuando "C puro"

Si tiene una función en C etiquetada con la cdefpalabra clave, con todas sus variables y llamadas de función en línea a otras cosas que son C pura, se ejecutará tan rápido como C pueda hacerlo. Pero si esa función hace referencia a cualquier código nativo de Python, como una estructura de datos de Python o una llamada a una API interna de Python, esa llamada será un cuello de botella en el rendimiento.

Afortunadamente, Cython proporciona una forma de detectar estos cuellos de botella: un informe de código fuente que muestra de un vistazo qué partes de su aplicación Cython son C pura y qué partes interactúan con Python. Cuanto mejor optimizada sea la aplicación, menor interacción habrá con Python.

Cython NumPy 

Cython mejora el uso de bibliotecas de procesamiento de números de terceros basadas en C como NumPy. Debido a que el código Cython se compila en C, puede interactuar con esas bibliotecas directamente y eliminar los cuellos de botella de Python.

Pero NumPy, en particular, funciona bien con Cython. Cython tiene soporte nativo para construcciones específicas en NumPy y proporciona acceso rápido a matrices NumPy. Y la misma sintaxis familiar de NumPy que usaría en un script Python convencional se puede usar en Cython tal cual.

Sin embargo, si desea crear los enlaces más cercanos posibles entre Cython y NumPy, debe decorar aún más el código con la sintaxis personalizada de Cython. La  cimportdeclaración, por ejemplo, permite que el código Cython vea construcciones de nivel C en bibliotecas en tiempo de compilación para los enlaces más rápidos posibles.

Dado que NumPy se usa tan ampliamente, Cython admite NumPy "listo para usar". Si tiene NumPy instalado, puede simplemente indicar  cimport numpy en su código, luego agregar más decoración para usar las funciones expuestas. 

Perfilado y rendimiento de Cython

Obtiene el mejor rendimiento de cualquier fragmento de código al perfilarlo y ver de primera mano dónde están los cuellos de botella. Cython proporciona enlaces para el módulo cProfile de Python, por lo que puede usar las propias herramientas de creación de perfiles de Python, como cProfile, para ver cómo funciona su código Cython. 

Es útil recordar en todos los casos que Cython no es mágico, que aún se aplican prácticas sensatas de rendimiento en el mundo real. Cuanto menos te muevas de un lado a otro entre Python y Cython, más rápido se ejecutará tu aplicación.

Por ejemplo, si tiene una colección de objetos que desea procesar en Cython, no la repita en Python e invoque una función Cython en cada paso. Pase la colección completa a su módulo Cython e itere allí. Esta técnica se usa a menudo en bibliotecas que administran datos, por lo que es un buen modelo para emular en su propio código.

Usamos Python porque brinda comodidad al programador y permite un desarrollo rápido. A veces, la productividad del programador tiene como costo el rendimiento. Con Cython, un pequeño esfuerzo adicional puede brindarle lo mejor de ambos mundos.

Leer más sobre Python

  • ¿Qué es Python? Programación potente e intuitiva
  • ¿Qué es PyPy? Python más rápido sin dolor
  • ¿Qué es Cython? Python a la velocidad de C
  • Tutorial de Cython: Cómo acelerar Python
  • Cómo instalar Python de forma inteligente
  • Las mejores características nuevas en Python 3.8
  • Mejor gestión de proyectos de Python con Poetry
  • Virtualenv y venv: explicación de los entornos virtuales de Python
  • Python virtualenv y venv qué hacer y qué no hacer
  • Explicación de subprocesos y subprocesos de Python
  • Cómo usar el depurador de Python
  • Cómo usar timeit para perfilar el código Python
  • Cómo usar cProfile para perfilar el código Python
  • Comience con async en Python
  • Cómo usar asyncio en Python
  • Cómo convertir Python a JavaScript (y viceversa)
  • Python 2 EOL: Cómo sobrevivir al final de Python 2
  • 12 pitones para cada necesidad de programación
  • 24 bibliotecas de Python para cada desarrollador de Python
  • 7 dulces IDE de Python que quizás te hayas perdido
  • 3 deficiencias principales de Python y sus soluciones
  • 13 marcos web de Python comparados
  • 4 marcos de prueba de Python para acabar con sus errores
  • 6 fantásticas funciones nuevas de Python que no querrá perderse
  • 5 distribuciones de Python para dominar el aprendizaje automático
  • 8 fantásticas bibliotecas de Python para el procesamiento del lenguaje natural