1 puntos por GN⁺ 2024-03-12 | 1 comentarios | Compartir por WhatsApp
  • El PR #116338 de CPython fusionó en python:main un cambio que permite desactivar el GIL en compilaciones free-threaded con PYTHON_GIL=0 o -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() y drop_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 en test_asyncio
  • Durante la revisión se agregaron pruebas para PYTHON_GIL, documentación, la opción -X gil y su reflejo en sys.flags; también se corrigió el manejo de la configuración para que PYTHON_GIL=1 fuerce 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
  • colesbury lo fusionó en python:main el 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() y drop_gil() retornan antes
  • Durante la revisión también se agregó un commit que configura correctamente enable_gil cuando PYTHON_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 nogil se programaron varias veces pruebas de builders relacionados con NoGIL
    • x86-64 MacOS Intel ASAN NoGIL PR
    • x86-64 MacOS Intel NoGIL PR
    • ARM64 MacOS M1 Refleaks NoGIL PR
    • ARM64 MacOS M1 NoGIL PR
    • AMD64 Ubuntu NoGIL Refleaks PR
    • AMD64 Ubuntu NoGIL PR
    • AMD64 Windows Server 2022 NoGIL PR

Alcance agregado durante la revisión

  • corona10 sugirió que valía la pena agregar pruebas de la variable de entorno en Lib/test/test_cmd_line.py
  • Luego se agregaron los siguientes commits
    • Add test for PYTHON_GIL in test_cmd_line
    • Set enable_gil properly when PYTHON_GIL=1
    • Don't add 'enable_gil' to test_embed in normal builds
  • colesbury consideró conveniente documentar la variable de entorno al momento de agregarla
    • Se basó en que la bandera de configure --disable-gil ya estaba documentada
    • Resumió que la documentación debía incluir que solo está disponible en compilaciones free-threaded, que 0 fuerza la desactivación del GIL, que 1 fuerza la activación del GIL y que es una novedad de Python 3.13
  • 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 -X para usar junto con la variable de entorno
  • El título del PR cambió de una forma que solo cubría PYTHON_GIL=0 a una que incluye también PYTHON_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 option
    • Fix link to -X gil
    • Fix PYTHON_GIL versionchanged line
    • Clarify test_flags in normal builds
  • ericsnowcurrently, erlend-aasland, corona10 y colesbury aprobaron los cambios
  • El commit de fusión es 2731913; después de la fusión, vstinner reaccionó diciendo que este cambio era “interesante y muy aterrador”

Trabajo posterior

  • Se separaron dos tareas como issues posteriores
    • #116322: trabajo para volver a activar el GIL al cargar extensiones incompatibles
    • #116329: trabajo para desactivar el GIL por defecto
  • 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 -X en compilaciones free-threaded

1 comentarios

 
GN⁺ 2024-03-12
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

  • 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

  • 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 el futuro CPython podría implementar transferencia de propiedad, pero es algo un poco más complicado
      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?

    • Antes, por el GIL, en la práctica casi no se escribía Python multihilo. Los hilos se usaban sobre todo para manejar múltiples tareas independientes que podían quedar bloqueadas por entrada/salida; eso, por supuesto, es común y útil, pero no ayudaba al rendimiento del código Python centrado en CPU
      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....
    • Si algún paquete depende del GIL, el GIL se activa. El paquete no se rompe
  • 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