gh-116167: Permitir desactivar el GIL
(github.com/python)- El PR #116338 de CPython fusionó en
python:mainun cambio que permite desactivar el GIL en compilaciones free-threaded conPYTHON_GIL=0o-X gil=0 - Para dejar abierta la posibilidad de volver a activar el GIL en tiempo de ejecución, las estructuras de datos relacionadas con el GIL se inicializan como siempre, y la desactivación se maneja estableciendo una bandera al inicio para que
take_gil()ydrop_gil()retornen antes - En las verificaciones iniciales, con
PYTHON_GIL=0, algunas pruebas y programas pequeños que no usan threads funcionaron correctamente; programas con threads muy básicos a veces funcionaron, pero la suite completa de pruebas crasheó rápidamente entest_asyncio - Durante la revisión se agregaron pruebas para
PYTHON_GIL, documentación, la opción-X gily su reflejo ensys.flags; también se corrigió el manejo de la configuración para quePYTHON_GIL=1fuerce la activación del GIL - El trabajo posterior se separó en el problema de volver a activar el GIL al cargar extensiones incompatibles y el de desactivar el GIL por defecto; este cambio agrega una superficie de control del GIL en las compilaciones free-threaded de Python 3.13
Cambios fusionados
- El PR #116338 de CPython trata el cambio
gh-116167: Allow disabling the GIL with PYTHON_GIL=0 or -X gil=0 colesburylo fusionó enpython:mainel 11 de marzo de 2024- El tamaño del cambio se muestra como 12 archivos, 163 líneas agregadas y 1 línea eliminada
- La funcionalidad objetivo no es para compilaciones normales, sino una opción de ejecución para desactivar el GIL en compilaciones free-threaded
Cómo se desactiva el GIL
- En compilaciones free-threaded, se puede desactivar el GIL con las siguientes configuraciones
PYTHON_GIL=0-X gil=0
- Para poder volver a activar el GIL en tiempo de ejecución, todas las estructuras de datos relacionadas con el GIL se inicializan como siempre
- La desactivación real se hace estableciendo una bandera al inicio
- Debido a esta bandera,
take_gil()ydrop_gil()retornan antes
- Debido a esta bandera,
- Durante la revisión también se agregó un commit que configura correctamente
enable_gilcuandoPYTHON_GIL=1
Pruebas y limitaciones actuales
- Se revisaron algunas pruebas y programas pequeños con la configuración
PYTHON_GIL=0- Se confirmó que las pruebas y programas pequeños que no usan threads funcionan correctamente
- Programas con threads muy básicos a veces funcionan
- La suite completa de pruebas crasheó rápidamente, y la ubicación quedó registrada como
test_asyncio - Con el comando
!buildbot nogilse programaron varias veces pruebas de builders relacionados con NoGILx86-64 MacOS Intel ASAN NoGIL PRx86-64 MacOS Intel NoGIL PRARM64 MacOS M1 Refleaks NoGIL PRARM64 MacOS M1 NoGIL PRAMD64 Ubuntu NoGIL Refleaks PRAMD64 Ubuntu NoGIL PRAMD64 Windows Server 2022 NoGIL PR
Alcance agregado durante la revisión
corona10sugirió que valía la pena agregar pruebas de la variable de entorno enLib/test/test_cmd_line.py- Luego se agregaron los siguientes commits
Add test for PYTHON_GIL in test_cmd_lineSet enable_gil properly when PYTHON_GIL=1Don't add 'enable_gil' to test_embed in normal builds
colesburyconsideró conveniente documentar la variable de entorno al momento de agregarla- Se basó en que la bandera de configure
--disable-gilya estaba documentada - Resumió que la documentación debía incluir que solo está disponible en compilaciones free-threaded, que
0fuerza la desactivación del GIL, que1fuerza la activación del GIL y que es una novedad de Python 3.13
- Se basó en que la bandera de configure
- Luego se agregó el commit
Document PYTHON_GIL environment variable
Incorporación de la opción -X gil y fusión final
- Después de una discusión en Discord, se decidió agregar también una opción
-Xpara usar junto con la variable de entorno - El título del PR cambió de una forma que solo cubría
PYTHON_GIL=0a una que incluye tambiénPYTHON_GIL=0 or -X gil=0 - Los commits adicionales incluyeron lo siguiente
Add -X gil option, add to sys.flags, modify test to cover env var… and optionFix link to -X gilFix PYTHON_GIL versionchanged lineClarify test_flags in normal builds
ericsnowcurrently,erlend-aasland,corona10ycolesburyaprobaron los cambios- El commit de fusión es
2731913; después de la fusión,vstinnerreaccionó diciendo que este cambio era “interesante y muy aterrador”
Trabajo posterior
- Se separaron dos tareas como issues posteriores
- Este PR no cambia el valor predeterminado del GIL, sino que permite a los usuarios controlar el estado del GIL mediante una variable de entorno o la opción
-Xen compilaciones free-threaded
1 comentarios
Opiniones en Hacker News
Dejo enlaces adicionales para quienes tengan curiosidad sobre el trabajo de no-GIL: [0], [1]
[0] Multithreaded Python without the GIL
https://docs.google.com/document/d/18CXhDb1ygxg-YXNBJNzfzZsD...
[1] Github repo
https://github.com/colesbury/nogil
[0] https://peps.python.org/pep-0703/
[1] https://github.com/colesbury/nogil-3.12
Me entusiasma ver cuánto más rápido se podrá hacer el Python base. Su propuesta de valor también se está viendo cuestionada a medida que aparecen demasiadas herramientas que intentan mitigar ese problema
Como herramientas para mejorar la velocidad se me vienen a la mente Mojo, pytorch, triton, numba y taichi. Hay tantos intentos de resolver este problema que, la última vez que quise probar uno, me abrumó la cantidad de opciones. Al final elegí taichi y fue bastante divertido y fácil de usar, aunque su alcance de aplicación era algo limitado
Taichi está realmente subestimado. Funciona en todas las plataformas, incluido Metal, tiene muchos ejemplos y es fácil escribir código. Sobre todo, se integra con el ecosistema y no lo reemplaza
https://github.com/taichi-dev
Un excelente video demo que muestra qué se puede hacer con Taichi: https://www.youtube.com/watch?v=oXRJoQGCYFg
https://www.youtube.com/watch?v=WNh4Q7-OSJs
https://www.taichi-lang.org/
Me pregunto por qué el esquema de conteo de referencias sesgado descrito en https://peps.python.org/pep-0703/ solo mantiene afinidad con un único hilo y exige incrementos/decrementos atómicos cuando se accede desde otros hilos
En otras implementaciones, por ejemplo en varias crates de Rust que implementan conteo de referencias sesgado, he visto un enfoque en el que solo se incrementa atómicamente al moverlo a un nuevo hilo, y luego ese hilo hace incrementos/decrementos no atómicos hasta volver a llegar a 0, para finalmente hacer un decremento atómico. Me pregunto si es porque esto se está agregando sobre un sistema existente: hay un único PyObject y no se lo puede reemplazar para que apunte a un nuevo objeto local del hilo
En Rust, el "move" para transferir propiedad forma parte del lenguaje, pero en C o Python no existe un concepto equivalente, por lo que es difícil determinar cuándo transferir la propiedad y qué hilo debería convertirse en el nuevo propietario. Se podrían usar heurísticas. Por ejemplo, al poner un objeto en queue.SimpleQueue se podría renunciar a la propiedad o transferirla, pero incluso en ese caso es difícil saber de antemano qué hilo hará "get" del objeto en la cola
Además, la ganancia de rendimiento probablemente sea pequeña. Muchos objetos se acceden desde un solo hilo, y algunos objetos se acceden desde varios hilos, pero son raros los objetos a los que primero accede exclusivamente un hilo y luego exclusivamente otro
Primero leí la noticia de tranched bread, ¿y ahora esto también? Qué época increíble
Me dio algo de pena cuando el proyecto Unladen Swallow [1] se fue apagando. Es bueno ver a Python volver al camino de la optimización del núcleo
[1] https://en.wikipedia.org/wiki/CPython#Unladen_Swallow
Me gustaría que me lo explicaran como si tuviera cinco años
Entiendo conceptualmente qué es el GIL. Pero ¿cuál es el impacto de este cambio? ¿Ahora los paquetes se van a romper a cambio de esperar una mejora general de rendimiento?
Incluso si no son tareas intensivas de CPU, este cambio puede ser útil. Últimamente mucho código se escribe con las funciones nativas de asyncio de Python. Esto, como NodeJS, funciona en un solo hilo cediendo la ejecución con async/await, y con un solo hilo puede lograr un rendimiento bastante bueno, del orden de miles de solicitudes por segundo
Pero el gran problema es que, en cuanto se realiza cualquier tarea de CPU, se bloquean todas las demás corrutinas, lo que genera todo tipo de problemas poco claros y arruina las solicitudes por segundo. Por ejemplo, puedes ver timeouts aleatorios de entrada/salida en una corrutina, cuando la causa real puede ser que otra corrutina completamente distinta acaparó la CPU por un momento. También es muy difícil observar por qué sucede esto. asyncio ofrece la función
asyncio.to_thread()[1], que ayuda a sacar las tareas bloqueantes del hilo principal, pero por culpa del GIL no las separa realmente para que una tarea centrada en CPU no interfiera con otras corrutinas[1] https://docs.python.org/3/library/asyncio-task.html#asyncio....
Para quienes tengan curiosidad, GIL significa Global Interpreter Lock
¿Hay algún recurso que resuma bien el panorama general aquí?
Por fin espero ver benchmarks de varias herramientas