Las mejoras de la próxima (y futuras) versión de Python están preparadas para acelerarlo, adelgazarlo y allanar el camino hacia cosas aún mejores.
Dado que Python es un lenguaje dinámico, hacerlo más rápido ha sido todo un reto. Pero en los últimos dos años, los desarrolladores del equipo central de Python se han centrado en varias formas de hacerlo.
En la PyCon 2023, celebrada en Salt Lake City, Utah, varias charlas destacaron el futuro de Python como lenguaje más rápido y eficiente. Python 3.12 mostrará muchas de esas mejoras. Algunas son nuevas en esa última versión, otras ya están en Python pero se han refinado aún más.
Puede leer también | 10 bibliotecas de Python para mejorar la accesibilidad de la IA
Mark Shannon, un antiguo colaborador de Python que ahora trabaja en Microsoft, resumió muchas de las iniciativas para acelerar y agilizar Python. La mayor parte del trabajo que describió en su presentación se centró en reducir el uso de memoria de Python, hacer más rápido el intérprete y optimizar el compilador para obtener un código más eficiente.
Otros proyectos, aún en secreto pero ya prometedores, ofrecen formas de ampliar el modelo de concurrencia de Python. Esto permitirá a Python utilizar mejor múltiples núcleos con menos de las compensaciones impuestas por los hilos, async, o multiprocesamiento.
El GIL por intérprete y los subinterpretes
¿Qué impide que Python sea realmente rápido? Una de las respuestas más comunes es "la falta de una forma mejor de ejecutar código a través de múltiples núcleos". Python tiene multihilo, pero los hilos se ejecutan de forma cooperativa, cediéndose unos a otros para el trabajo limitado a la CPU. Y el soporte de Python para el multiprocesamiento es muy pesado: hay que crear varias copias del tiempo de ejecución de Python para cada núcleo y distribuir el trabajo entre ellas.
Puede leer también | ¿Cuáles son los puntos débiles más comunes de la programación en Python?
Una forma largamente soñada de resolver este problema es eliminar el GIL (Global Interpreter Lock) de Python. El GIL sincroniza las operaciones entre hilos para asegurar que los objetos son accedidos por un solo hilo a la vez. En teoría, eliminar el GIL permitiría un verdadero multihilo. En la práctica -y se ha probado muchas veces- ralentiza los casos de uso sin hilos, por lo que no es una ganancia neta.
Eric Snow, desarrollador del núcleo de python, desveló en su charla una posible solución futura para todo esto: subinterpretadores y un GIL por intérprete. En resumen: el GIL no sería eliminado, sólo eludido.
Subinterpreters es un mecanismo por el cual el tiempo de ejecución de Python puede tener múltiples intérpretes ejecutándose juntos dentro de un único proceso, en lugar de que cada intérprete esté aislado en su propio proceso (el mecanismo actual de multiprocesamiento). Cada subinterpretador tiene su propio GIL, pero todos los subinterpretadores pueden compartir estado más fácilmente.
Puede leer también | Los 10 usos principales de ChatGPT para programadores de Python
Aunque los subinterpretadores han estado disponibles en el tiempo de ejecución de Python desde hace algún tiempo, no han tenido una interfaz para el usuario final. Además, el desordenado estado interno de Python no ha permitido que los subinterpretadores se utilicen eficazmente.
Con Python 3.12, Snow y su cohorte han limpiado el interior de Python lo suficiente como para que los subinterpretadores sean útiles, y han añadido un módulo mínimo a la biblioteca estándar de Python llamado intérpretes. Esto proporciona a los programadores una forma rudimentaria de lanzar subinterpretadores y ejecutar código en ellos.
Los experimentos iniciales del propio Snow con subinterpretadores superaron significativamente el rendimiento de los hilos y el multiprocesamiento. Un ejemplo, un sencillo servicio web que realizaba algunas tareas limitadas a la CPU, llegaba a un máximo de 100 peticiones por segundo con hilos y 600 con multiprocesamiento. En cambio, con subinterpretadores, se obtuvieron 11.500 peticiones, y sin apenas caídas cuando se amplió a partir de un cliente.
En la actualidad, el módulo de intérpretes tiene una funcionalidad muy limitada y carece de mecanismos sólidos para compartir el estado entre subinterpretadores. Pero Snow cree que para Python 3.13 aparecerá mucha más funcionalidad, y mientras tanto se anima a los desarrolladores a experimentar.
Un intérprete de Python más rápido
Otro importante conjunto de mejoras de rendimiento que mencionó Shannon, el nuevo intérprete especializado adaptable de Python, fue tratado en detalle en una sesión aparte por Brandt Bucher, desarrollador principal de Python.
Python 3.11 introdujo nuevos bytecodes en el intérprete, denominados instrucciones adaptativas. Estas instrucciones pueden sustituirse automáticamente en tiempo de ejecución por versiones especializadas para un tipo determinado de Python, un proceso denominado quickening. Esto ahorra al intérprete el paso de tener que buscar de qué tipo son los objetos, acelerando enormemente todo el proceso. Por ejemplo, si una operación de suma toma regularmente dos enteros, esa instrucción puede ser reemplazada por otra que asuma que los operandos son ambos enteros.
Puede leer también | Ciberatacantes piratean un proyecto de aprendizaje automático en Python
Sin embargo, no todo el código se especializa bien. Por ejemplo, la aritmética entre ints y floats está permitida en Python, pero las operaciones entre ints e ints, o floats e ints, no se especializan bien. Bucher proporciona una herramienta llamada specialist, disponible en PyPI, para determinar si el código se especializa bien o mal, y para sugerir dónde se puede mejorar. Más información: https://github.com/brandtbucher/specialist
Python 3.12 tiene más opcodes de especialización adaptables, como los accesores para atributos dinámicos, que son operaciones lentas. La versión 3.12 también simplifica el proceso general de especialización, con menos pasos.
El gran adelgazamiento de los objetos Python
Los objetos Python han utilizado históricamente mucha memoria. La cabecera de un objeto Python 3, incluso sin los datos del objeto, ocupaba 208 bytes.
En las últimas versiones de Python, sin embargo, se han realizado varios esfuerzos para racionalizar el diseño de los objetos Python, encontrando formas de compartir memoria o representar las cosas de forma más compacta. Shannon explicó cómo, a partir de Python 3.12, la cabecera del objeto ocupa ahora sólo 96 bytes, algo menos de la mitad que antes.
Puede leer también | ¿Cómo crear un modelo de inteligencia artificial con el lenguaje de programación Python?
Estos cambios no sólo permiten mantener más objetos Python en memoria, sino que también mejoran la localización en caché de los objetos Python. Aunque esto por sí mismo no acelere las cosas tan significativamente como otros esfuerzos, sigue siendo una gran ayuda.
Python a prueba de futuro
La implementación por defecto de Python, CPython, tiene tres décadas de desarrollo a sus espaldas. Eso también significa tres décadas de desechos, API heredadas y decisiones de diseño que pueden ser difíciles de trascender, todo lo cual dificulta la mejora de Python en aspectos clave.
El desarrollador del núcleo de Python Victor Stinner, en una presentación sobre cómo las características de Python quedan obsoletas con el tiempo, se refirió a algunas de las formas en que las funciones internas de Python se están limpiando y preparando para el futuro.
Puede leer también | Guido van Rossum habla sobre el futuro de Python
Una cuestión clave es la proliferación de API de C en CPython, el tiempo de ejecución de referencia del lenguaje. A partir de la versión 3.8 de Python, existen varios conjuntos diferentes de API, cada uno con requisitos de mantenimiento distintos. En los últimos cinco años, Stinner ha trabajado para que muchas API públicas sean privadas, de modo que los programadores no tengan que tratar tan directamente con los aspectos internos sensibles de CPython. El objetivo a largo plazo es hacer que los componentes que utilizan las API de C, como los módulos de extensión de Python, dependan menos de cosas que podrían cambiar con cada versión.
Un proyecto de terceros llamado HPy pretende aliviar la carga de mantenimiento del desarrollador. HPy es un sustituto de la API de C para Python, más estable entre versiones, que proporciona un código más rápido en tiempo de ejecución y que se abstrae de las a menudo complicadas funciones internas de CPython. El inconveniente es que es un proyecto opcional, no un requisito, pero varios proyectos clave como NumPy están experimentando con su uso, y algunos (como el puerto HPy de ultrajson) están disfrutando de grandes ganancias de rendimiento como resultado.
Puede leer también | ¿Cuál debería usar para las aplicaciones web Node.js o Python?
La mayor ventaja de limpiar la API de C es que abre la puerta a muchos más tipos de mejoras que antes no eran posibles. Como todas las demás mejoras descritas aquí, se trata de allanar el camino hacia futuras versiones de Python que funcionen más rápido y más eficientemente que nunca.