Python se ha ganado la reputación de ser potente, flexible y fácil de trabajar. Estas virtudes han llevado a su uso en una enorme y creciente variedad de aplicaciones, flujos de trabajo y campos. Pero el diseño del lenguaje -su naturaleza interpretada, su dinamismo en tiempo de ejecución- significa que Python siempre ha sido un orden de magnitud más lento que los lenguajes nativos de máquina como C o C++.
A lo largo de los años, los desarrolladores han ideado una variedad de soluciones para las limitaciones de velocidad de Python. Por ejemplo, se pueden escribir tareas de alto rendimiento en C y envolverlas con Python; muchas bibliotecas de aprendizaje automático hacen exactamente esto. O puedes usar Cython, un proyecto que te permite espolvorear el código Python con información de tipo en tiempo de ejecución que le permite ser compilado a C.
Pero las soluciones nunca son ideales. ¿No sería genial si pudiéramos tomar un programa Python existente tal cual, y ejecutarlo dramáticamente más rápido? Eso es exactamente lo que PyPy le permite hacer.
PyPy vs. CPython
PyPy es un reemplazo para el intérprete de Python, CPython. Mientras que CPython compila Python a un código de bytes intermedio que luego es interpretado por una máquina virtual, PyPy utiliza la compilación justo a tiempo (JIT) para traducir el código Python a un lenguaje ensamblador nativo de la máquina.
Dependiendo de la tarea que se realice, las ganancias de rendimiento pueden ser espectaculares. En promedio, PyPy acelera Python unas 7,6 veces, con algunas tareas aceleradas 50 veces o más. El intérprete de CPython simplemente no realiza el mismo tipo de optimizaciones que PyPy, y probablemente nunca lo hará, ya que no es uno de sus objetivos de diseño.
La mejor parte es que se requiere poco o ningún esfuerzo por parte del desarrollador para desbloquear las ganancias que PyPy proporciona. Basta con cambiar CPython por PyPy, y en su mayor parte ya está hecho. Hay algunas excepciones, que se discuten a continuación, pero el objetivo declarado de PyPy es ejecutar el código Python existente, sin modificar, y proporcionarle un aumento automático de la velocidad.
PyPy actualmente soporta tanto Python 2 como Python 3, a través de diferentes encarnaciones del proyecto. En otras palabras, necesitas descargar diferentes versiones de PyPy dependiendo de la versión de Python que vayas a ejecutar. La rama Python 2 de PyPy ha existido durante mucho más tiempo, pero la versión Python 3 ha sido actualizada últimamente. Actualmente soporta tanto Python 3.5 (calidad de producción) como Python 3.6 (calidad beta).
Además de soportar todo el núcleo del lenguaje Python, PyPy funciona con la gran mayoría de las herramientas del ecosistema Python, como pip
para empaquetar o virtualenv
para entornos virtuales. La mayoría de los paquetes de Python, incluso los que tienen módulos de C, deberían funcionar tal cual, aunque hay limitaciones que veremos a continuación.
Cómo funciona PyPy
PyPy utiliza técnicas de optimización que se encuentran en otros compiladores just-in-time para lenguajes dinámicos. Analiza los programas Python en ejecución para determinar la información de tipo de los objetos a medida que se crean y utilizan en los programas, y luego utiliza esa información de tipo como guía para acelerar las cosas. Por ejemplo, si una función de Python trabaja sólo con uno o dos tipos de objetos diferentes, PyPy genera código máquina para manejar esos casos específicos.
Las optimizaciones de PyPy se manejan automáticamente en tiempo de ejecución, por lo que generalmente no es necesario ajustar su rendimiento. Un usuario avanzado podría experimentar con las opciones de la línea de comandos de PyPy para generar un código más rápido para casos especiales, pero sólo en raras ocasiones es necesario.
PyPy también se aleja de la forma en que CPython maneja algunas funciones internas, pero trata de preservar comportamientos compatibles. Por ejemplo, PyPy maneja la recolección de basura de manera diferente a CPython. No todos los objetos se recogen inmediatamente una vez que salen del ámbito, por lo que un programa Python que se ejecuta bajo PyPy puede mostrar una mayor huella de memoria que cuando se ejecuta bajo CPython. Pero aún puedes utilizar los controles de recolección de basura de alto nivel de Python expuestos a través del módulo gc
, como gc.enable()
, gc.disable()
y gc.collect()
.
Si quieres información sobre el comportamiento JIT de PyPy en tiempo de ejecución, PyPy incluye un módulo, pypyjit
, que expone muchos ganchos JIT a tu aplicación Python. Si tienes una función o módulo que parece estar funcionando mal con el JIT, pypyjit
te permite obtener estadísticas detalladas sobre él.
Otro módulo específico de PyPy, __pypy__
, expone otras características específicas de PyPy, por lo que puede ser útil para escribir aplicaciones que aprovechen esas características. Debido al dinamismo del tiempo de ejecución de Python, es posible construir aplicaciones Python que utilicen estas características cuando PyPy está presente y las ignoren cuando no lo está.
Limitaciones de PyPy
Por muy mágico que parezca PyPy, no lo es. PyPy tiene ciertas limitaciones que reducen u obvian su eficacia para ciertos tipos de programas. Por desgracia, PyPy no es un reemplazo completamente universal para el tiempo de ejecución de CPython.
PyPy funciona mejor con aplicaciones de Python puro
PyPy siempre ha funcionado mejor con aplicaciones de Python «puro» – es decir, aplicaciones escritas en Python y nada más. Los paquetes de Python que se interconectan con bibliotecas de C, como NumPy, no han funcionado tan bien debido a la forma en que PyPy emula las interfaces binarias nativas de CPython.
Los desarrolladores de PyPy han eliminado este problema y han hecho que PyPy sea más compatible con la mayoría de los paquetes de Python que dependen de extensiones de C. Numpy, por ejemplo, funciona muy bien con PyPy ahora. Pero si quieres la máxima compatibilidad con las extensiones de C, utiliza CPython.
PyPy funciona mejor con programas de larga duración
Uno de los efectos secundarios de cómo PyPy optimiza los programas de Python es que los programas de larga duración se benefician más de sus optimizaciones. Cuanto más tiempo se ejecute el programa, más información de tipo en tiempo de ejecución puede reunir PyPy, y más optimizaciones puede hacer. Los scripts de Python de una sola vez no se beneficiarán de este tipo de cosas. Las aplicaciones que sí se benefician suelen tener bucles que se ejecutan durante largos periodos de tiempo, o se ejecutan continuamente en segundo plano-los frameworks web, por ejemplo.
PyPy no hace compilación anticipada
PyPy compila código Python, pero no es un compilador para código Python. Debido a la forma en que PyPy realiza sus optimizaciones y al dinamismo inherente de Python, no hay forma de emitir el código JIT resultante como un binario independiente y reutilizarlo. Cada programa tiene que ser compilado para cada ejecución. Si quieres compilar Python en un código más rápido que pueda ejecutarse como una aplicación independiente, utiliza Cython, Numba o el proyecto Nuitka, actualmente experimental.