1 puntos por GN⁺ 2023-10-22 | 1 comentarios | Compartir por WhatsApp
  • El steering council de Python expresó su intención de aprobar la PEP 703 para hacer que el GIL sea opcional a lo largo de varias versiones, y las condiciones finales aún se están afinando
  • La compilación --disable-gil de CPython 3.13 se prepara como experimental, y la stable ABI y la compatibilidad de wheels de módulos de extensión aparecen como los principales retos técnicos
  • Como los wheels abi3 actuales podrían no encajar tal cual en CPython 3.13 sin GIL, se está discutiendo introducir abi4, cambios en la C API limitada y convertir macros de conteo de referencias en llamadas a función
  • Existe preocupación de que pip pueda elegir mal los wheels para compilaciones con GIL y sin GIL, y una instalación incorrecta silenciosa sería más peligrosa que un simple fallo de instalación
  • Para el nombre de la compilación sin GIL se propuso free-threading en lugar de nogil, pero también hay que resolver en conjunto el nombre del ejecutable, el shebang, la instalación en paralelo y el empaquetado en distribuciones

Dónde está hoy PEP 703 y el CPython sin GIL

  • El steering council de Python expresó a fines de julio su intención de aprobar la PEP 703, que propone hacer opcional el bloqueo global del intérprete (GIL) en CPython
  • Las condiciones detalladas de esa aprobación todavía no están definidas, pero la discusión sobre la implementación y la preparación del ecosistema ya están en marcha
  • A largo plazo se contempla un camino hacia una sola versión de CPython sin GIL, pero por ahora la etapa es probar el funcionamiento sin GIL en un intérprete compilado con la opción --disable-gil
  • CPython 3.13 está previsto para octubre de 2024, y la compilación sin GIL de esa versión tendrá carácter experimental

stable ABI y compatibilidad de módulos de extensión

  • Sam Gross trató en el foro de discusión de Python cómo encajan la PEP 703 y la stable ABI de CPython
  • La stable ABI permite que los módulos de extensión funcionen con el mismo wheel binario en varias versiones de CPython, evitando tener que recompilar en cada nueva versión
  • Las extensiones compiladas para la stable ABI podrían no funcionar tal cual en la compilación sin GIL de CPython 3.13
  • Para resolverlo, se propusieron algunas adiciones y cambios en la C API limitada
    • Las extensiones que usan solo la C API limitada pueden crear binarios que usen la stable ABI
    • Entre los cambios propuestos está un plan ya existente para convertir algunas macros que incrementan o decrementan el conteo de referencias de objetos en llamadas a función
  • El objetivo es hacer posible binarios de extensiones que funcionen tanto en compilaciones con GIL como sin GIL

abi3, abi4 y el problema de selección de wheels

  • Victor Stinner considera que, para que el experimento sin GIL tenga éxito, hace falta una solución simple para extensiones que funcionen en ambos tipos de intérprete
  • Como las extensiones compiladas para la stable ABI en CPython 3.12 o anterior no son compatibles con compilaciones sin GIL a partir de 3.13, surgió la idea de crear una nueva versión ABI: abi4
    • La stable ABI actual es abi3
    • El número de ABI no necesariamente está ligado al número de versión principal de CPython
  • Gross considera aceptable que las extensiones que quieran soportar no-GIL tengan que asumir en cierta medida la carga de crear dos wheels binarios
    • Le preocupa más que el proyecto no-GIL termine demasiado atado a mejoras en la C API y la stable ABI
  • Alex Gaynor, que también mantiene varios paquetes con wheels abi3, cree que generar dos wheels una vez no es una carga excesiva
    • Pero sí es importante que pip, actual y futuro, elija el wheel correcto entre ambos
  • Brett Cannon cree que la lógica actual de pip no distingue entre esas dos variantes, así que sin un cambio como abi4, las versiones actuales y antiguas de pip no se comportarán correctamente

Preocupación por fallos silenciosos en pip

  • Gross considera que no hace falta preocuparse demasiado por el soporte de pip viejo en la compilación experimental --disable-gil de CPython 3.13
    • Señala que es común que pip antiguo se rompa con nuevas versiones de Python
    • Puso como ejemplo el caso en que pip==23.1.1 o anterior falla en CPython 3.13 por la ausencia de pkgutil.ImpImporter
  • El mantenedor de pip, Paul Moore, considera que romperse de forma explícita y instalar silenciosamente el paquete equivocado son problemas distintos
    • Hay usuarios que usan versiones antiguas de pip
    • El impacto para el usuario no es el mismo entre un fallo explícito y un error silencioso
  • Moore teme que si quienes quieran probar compilaciones no-GIL o free-threaded tienen que depurar problemas de compatibilidad ABI, eso les quite las ganas de seguir probando
  • Gaynor también cree que si pip falla en silencio en paquetes afectados, podrían llegar una avalancha de reportes

Instalación en paralelo y nombre del ejecutable

  • Barry Warsaw preguntó si hay planes para instalar lado a lado compilaciones con GIL y sin GIL en el mismo sistema
  • Gross respondió que la situación es comparable a instalar distintas versiones de Python
  • Cannon considera posible incluir ambos binarios en un solo wheel “fat”
    • Pero los nombres de los binarios dentro del wheel tendrían que ser distintos
  • La discusión sobre el nombre del ejecutable continuó en otro hilo
  • Paul Moore considera que los usuarios deberían poder probar fácilmente el modo sin GIL y elegir con facilidad entre GIL y no-GIL
    • Si ese proceso es difícil en Windows, macOS o Linux, podría afectar negativamente al proyecto no-GIL
    • Cuanto más fácil sea probarlo, mayor será la demanda de compilaciones sin GIL y también la presión sobre mantenedores de paquetes para ofrecer wheels compatibles

Debate de nombres: nogil vs free-threading

  • Barry Scott considera importante el nombre del ejecutable porque en la línea shebang hay que indicar qué intérprete invocar
    • Dio como ejemplo nombres como python-nogil3 y python-nogil3.13
  • Gregory P. Smith expresó la opinión personal de que, como la compilación sin GIL de CPython 3.13 es una función experimental, las distribuciones no deberían ponerla en el $PATH predeterminado
    • Tampoco le parece deseable que un nombre largo de ejecutable quede fijado en shebangs durante mucho tiempo
    • Propuso posponer la decisión del nombre de instalación hasta después de 3.14
  • El desarrollador de Fedora Petr Viktorin señaló que es bastante probable que las distribuciones quieran empaquetar un intérprete sin GIL para que los usuarios experimenten
  • Moore considera deseable poder indicar una compilación free-threaded con algo como #!/usr/bin/env python3.13-nogil
    • La necesidad surge de evitar rutas largas y poco intuitivas hardcodeadas
  • En un hilo sobre el instalador de Windows iniciado por Steve Dower, Smith explicó que el steering council quiere evitar el nombre nogil
    • La razón es que para la mayoría de desarrolladores fuera del core no comunica bien la idea, no deberían necesitar saber qué es el GIL y además usa una formulación negativa
    • Como alternativa se propuso el término free-threading
  • Gross cree que free-threading tampoco es fácil de entender para quienes están fuera del tema y que no es un término de uso amplio
  • En la discusión real hubo una clara preferencia por nombres cortos, y en ese aspecto nogil era el candidato más fuerte
  • Un cambio concreto ya reflejado fue modificar la etiqueta ABI de la compilación no-GIL de n a t
    • La t significa threading

La propuesta de abi4 y el trabajo pendiente

  • Gross y Viktorin discutieron los puntos problemáticos de la propuesta de cambios en la API, y ese feedback llevó a la propuesta de una nueva ABI: abi4
  • Gross creó un prototipo de la nueva ABI
  • Viktorin coincide en términos generales con el enfoque, aunque cree que todavía hay que ordenar mejor varios detalles
  • Stinner considera que hará falta una PEP para abi4, y Viktorin lo toma como una discusión pre-PEP
  • Hay confusión sobre las garantías de compatibilidad que ofrece la combinación de versión de la C API limitada con abi3, y eso también afecta la dirección de abi4
  • La investigación relacionada sigue en curso, y es posible que haya discusión presencial en el sprint de core developers de mediados de octubre

Redacción final de la aprobación e impacto a largo plazo

  • El trabajo en CPython no-GIL o free-threaded sigue avanzando, pero la aprobación final de la PEP 703 todavía está pendiente
  • La demora ya se alargó un poco, pero la PEP 703 y sus efectos podrían influir fuertemente en el desarrollo y el ecosistema de CPython durante más de cinco años
  • El steering council quiere dejar claros los criterios de aprobación
  • Thomas Wouters señaló que está puliendo la redacción exacta de la aprobación y tratando de aclarar varias decisiones
  • Parte de ese trabajo también podría avanzar durante el sprint de core developers

1 comentarios

 
GN⁺ 2023-10-22
Opiniones de Hacker News
  • Al ver las computadoras modernas, me da la impresión de que el paralelismo explícito podría ser un elemento más fundamental de la informática de lo que está de moda en los libros de texto.
    Quizá ya estemos en una etapa en la que siempre tengamos que escribir código paralelo explícitamente.

    • Como a las personas se les da mal razonar sobre varios hilos al mismo tiempo, es probable que el cambio más práctico vaya hacia la sintaxis declarativa, algo que ya se ve.
      Por ejemplo, los bucles for están siendo reemplazados por operaciones como foreach, map y filter. Estas expresiones le indican al compilador/intérprete la intención de aplicar una operación a todos los elementos de una estructura de datos, y dejan en manos del compilador/runtime si paralelizarlo y cómo hacerlo.
    • El paralelismo se ha dividido en varias direcciones.
      En la ejecución de servicios web, cada solicitud es lo bastante rápida, y el verdadero beneficio del paralelismo está en procesar muchas solicitudes en paralelo. Ahí encaja No-GIL.
      Cuando dentro de una sola solicitud hay muchas subsolicitudes, normalmente se maneja con código asíncrono, pero muchas veces eso se debe más a que crear hilos es caro o a que los pools de hilos son engorrosos que a las ventajas de rendimiento de lo asíncrono. Lo asíncrono es bueno para el throughput, pero malo para la latencia, y si se paralelizan solicitudes de un servicio, normalmente preocupa más la latencia. Lo asíncrono ganó sobre todo por usabilidad.
      Otro tipo de paralelismo aparece en trabajos offline a gran escala. Cosas como MapReduce o Presto, que en general se parecen a problemas de divide y vencerás. El entrenamiento de modelos en GPU es similar.
      Lo que no ocurrió fueron los algoritmos locales altamente paralelos. En los servicios web, el tamaño de los datos es pequeño, la ganancia de latencia es baja, la implementación es compleja y el costo de coordinación entre hilos aumenta. Una pequeña excepción son los algoritmos vectorizados, pero se ejecutan en un solo núcleo y no tienen overhead de coordinación; la inferencia online también vuelve a estar muy fuertemente vectorizada.
    • En informática, el paralelismo se parece un poco a la seguridad. Sabemos que es importante en abstracto, pero para aprenderlo bien hay que buscar formación aparte.
      Con el tiempo, ambos están mejorando. Así como más lenguajes y bibliotecas se vuelven seguros por defecto, ahora más cosas son paralelas por defecto. Aún falta camino, pero creo que fue bueno no haberlo hecho demasiado pronto, porque la tecnología mejoró mucho en los últimos 10 años.
      Por ejemplo, se puede comparar lo que se puede hacer de forma segura con Rayon de Rust con lo que se hacía de forma insegura con OpenMP en C++.
      Más hacia afuera también están estas cosas en las que trabajo: https://legion.stanford.edu/, https://regent-lang.org/, https://github.com/nv-legate/cunumeric
    • Veo el paralelismo como algo de la misma categoría que la gestión de memoria. La mayoría de los programas que escribimos pueden y deberían usar algún tipo de gestión automática, y la gestión manual puede quedar para las áreas donde sea necesaria por rendimiento.
      Como es un detalle de implementación, si se puede abstraer para que sea más fácil de aprovechar, deberíamos hacerlo.
    • La wiki de LMAX Disruptor dice que la latencia promedio para enviar un mensaje de un hilo a otro es de 53 nanosegundos.
      En comparación, un mutex ronda los 25 nanosegundos y aumenta si hay contención, pero un mutex es sincronización punto a punto.
      Lo bueno de Disruptor es que varios hilos pueden recibir el mismo mensaje sin mucho esfuerzo adicional.
      https://github.com/LMAX-Exchange/disruptor/wiki/Performance-...
      https://gist.github.com/rmacy/2879257
      Sueño con un lenguaje parecido a Smalltalk, pero que se mantenga en un solo hilo hasta que paralelizar tenga sentido.
      Estoy buscando problemas de paralelismo que no sean de big data. El paralelismo se parece más a poner más autos en la carretera que a aumentar la velocidad de un auto. Pero todavía estoy tratando de encontrar qué cosas deberían hacer localmente los usuarios de escritorio o móviles aprovechando la potencia matemática de sus computadoras.
      También estoy pensando en Itanium y las arquitecturas VLIW como ideas de paralelismo.
  • Se puede usar -ng, con el sentido de no-gil o next-generation.

    • Me recuerda a la gran ola de soporte de threading en Unix. Lo que tenía que hacer el desarrollador variaba muchísimo según la plataforma.
      Había nuevos flags de compilador, nuevos flags de linker, enlace con bibliotecas distintas e incluso el uso de comandos de compilador totalmente diferentes. AIX era especialmente así.
  • Para el problema del shebang, quizá convenga apoyarse en la convención existente de Python: from __future__ import nogil.
    En ese momento se podría hacer hot-swap del intérprete.

    • from __future__ import no es una sentencia de runtime, sino una sentencia especial que representa flags.
      https://docs.python.org/3/reference/simple_stmts.html#future...
    • “Una sentencia future es una directiva para el compilador que indica que un módulo específico debe compilarse con la sintaxis o la semántica que se usará en una futura versión de Python cuando esa funcionalidad pase a ser estándar”.
      Las sentencias future son por módulo, y GIL/no-GIL no encaja fácilmente en ese modelo.
    • Si no se ejecuta como el primer módulo y el primer import, la implementación podría convertirse en una pesadilla.
  • Cada vez que veo esta propuesta, me pregunto cómo van a garantizar que los programas sigan funcionando correctamente. Gran parte del código Python multihilo existente está escrito de forma insegura
    En particular, el problema son las carreras de datos, algo que he visto repetidamente en bases de código de varias empresas y en proyectos de código abierto. Esos programas no se rompen solo porque dependen implícitamente de que el GIL permite ejecutar a un solo hilo a la vez
    Si desaparece el GIL, esos programas se van a romper. Como Python es un lenguaje de tipado dinámico, dudo mucho que pueda existir un analizador estático que encuentre estos problemas en programas Python existentes
    Lo más probable son bugs sutiles que aparecen de forma no determinista en runtime. Ojalá al menos provocaran crashes, pero esta clase de bugs probablemente termine en comportamientos incorrectos
    Quizá esta propuesta sin GIL no esté pensada para usarse en la mayoría de los programas. Tal vez sea una herramienta ultraespecializada para un conjunto muy reducido de situaciones en las que el programador sabe que no hay GIL y puede escribir el código en consecuencia

    • Si tienes un programa multihilo con carreras de datos, ya tienes un problema. El GIL no hace que las carreras de datos sean imposibles
      El GIL solo significa que un único hilo puede ejecutar bytecode de Python a la vez. Un intérprete con GIL también puede cambiar de hilo entre bytecodes, y muchas operaciones de Python requieren varios bytecodes. Eso incluye métodos incorporados de tipos incorporados que mucha gente considera “atómicos”
      Por eso Python ofrece actualmente locks, mutexes, semáforos y cosas por el estilo, aun teniendo GIL
    • Como dato curioso: el GIL nunca evita todos los bugs de condiciones de carrera
      Los hilos que compiten por el GIL ya pueden quitárselo unos a otros en malos momentos y generar caos
    • Entendí que la idea central es hacer que las librerías declaren si soportan el modo nogil, es decir, que sea opt-in
      Si el programa solo se ejecuta sin GIL cuando todas sus dependencias lo permiten, debería haber tiempo suficiente para corregir esos bugs
    • Creo que Python sin GIL llegará, como mínimo, después de 3 o 4 ciclos de releases. Ya pasó un año desde que salió 3.11, y me parece que mucho código Python en producción todavía está alrededor de 3.8
      Entonces, probablemente no se empiece a tratar este problema a gran escala hasta cerca de 2030. Tampoco se ven muchas producciones que actualicen de inmediato el runtime que usan a la versión más reciente
      No quiero sonar duro, pero el Steering Council dijo que no quiere otra migración de 2 a 3, así que la gente no va a actualizar a la ligera. Gran parte de lo que hay hoy en línea podría ser peligroso para copiar y pegar
    • El GIL solo protege al intérprete. Lo único que puede hacer es reducir la frecuencia con la que aparecen los problemas
      En el código Python real hay muchísimos bugs de threading
  • ¿OCaml no pasó por una evolución parecida? Me pregunto si hay puntos comparables entre ambos proyectos

    • Yo diría que no. OCaml 5, en lugar de eliminar un lock global y romper código existente, introdujo una nueva primitiva llamada domain, que administra uno o más hilos con un lock compartido
      Así, la API de hilos existente crea hilos dentro del domain actual y permite aislar el código que espera poder tomar un lock. El código nuevo, en cambio, puede crear un nuevo domain que empieza con un solo hilo. También se pueden usar ambos intencionalmente como forma de scheduling
      Python está intentando hacer que el lock sea completamente opcional de forma global, fuera del control de los autores de librerías. Dicho eso, parece que en Python se garantiza que el lock solo protege al propio runtime, así que la mayoría del código que depende de ese lock probablemente ya tenga bugs de todos modos; por eso el plan de Python también parece viable
      Si hay algo en común, sería encontrar y corregir estado compartido inesperado en toda la base de código del runtime, y tener que revisar la ABI de C
  • Ahora Python también tiene oportunidad de alcanzar a Tcl en rendimiento multihilo: https://www.hammerdb.com/blog/uncategorized/why-tcl-is-700-f...

  • Preferiría portar código Python a Mojo para obtener multithreading, SIMD y otras mejoras de velocidad

    • Ojalá Mojo fuera un mundo más completo, pero por ahora no está ni cerca de ese nivel
    • De acuerdo. Preferiría reescribirlo en Rust, Nim o .NET
    • Que haya llegado a -3 votos negativos muestra muy bien que los downvotes de HN no tienen que ver con la lógica, sino con peleas territoriales y ego
      ¿No quieres mover código Python a Python nogil? Entonces te downvotean, algo así
  • Si hubiera que ponerle nombre, podrían ser cosas como python4, python3-gilfoil, python3-gilfree

  • El enfoque actual en Python sin GIL se siente bastante extraño. El equipo de Faster CPython se puso la ambiciosa meta de mejorar el rendimiento de CPython en un 50% en cada release
    En 3.11 sí hubo mejoras reales, pero quedaron muy lejos del 50%, y en bastantes de nuestras pruebas 3.12 fue similar o incluso más lento. El multithreading real sería genial, pero antes preferiría mucho más que mejorara el rendimiento single-thread
    Por supuesto, reconozco que nuestras necesidades quizá no representen a todos, y agradezco todo el trabajo que se ha hecho para convertir a Python en un gran lenguaje. Aun así, me pregunto qué me estoy perdiendo

    • Creo que Python necesita tener una respuesta urgente para el uso de múltiples cores. AMD acaba de lanzar un CPU de 96 cores
      Actualmente, el uso de múltiples cores se hace mediante multiprocessing, que tiene muchas limitaciones. Entiendo que varios intérpretes podrían llegar junto con algo como corrutinas, pero aun así me gusta más la opción de multithreading real
    • Son objetivos completamente distintos. En teoría, Python multithread puede hacer que ciertos programas sean más rápidos, pero la forma importa
      En nogil Python, por ejemplo, varios threads pueden llamar código C con estado compartido accesible como objetos de Python. Esto es bastante central para machine learning y, de hecho, la forma actual de este PEP vino del equipo de PyTorch
      El rendimiento single-thread también es importante, pero para las secciones críticas ya existían alternativas bastante decentes como numba, Cython y Mojo
      El orden también importa. Si se introduce nogil, una parte considerable del trabajo de faster CPython podría terminar descartándose por completo, así que los equipos tenían que coordinarse
      En un mundo ideal, habría tanto modo nogil como mejoras de rendimiento single-thread. Guido también insinuó que está considerando un JIT sofisticado
    • En “Python”, las partes con mayor costo computacional se manejan en librerías como numpy y tensorflow
      Python hace que sea muy cómodo manejar abstracciones de bajo nivel desde un lenguaje de más alto nivel. Por eso, como desarrollador de Python de muchos años, el GIL nunca me estresó demasiado
    • Parece que se te escapa que las necesidades de las personas citadas en el PEP 703 son distintas de las tuyas: <https://peps.python.org/pep-0703/#motivation>
    • Según entiendo, son proyectos separados dentro de CPython, y no necesariamente trabajan en ellos las mismas personas
      Si hubiera que elegir solo uno de los dos, estoy de acuerdo en que para la mayoría de los casos de uso encaja mejor simplemente tener código single-thread más rápido. Pero tampoco hay razón para no tener ambos
  • Con hindsight es claro, pero si el lado de Python hubiera sabido lo larga y dolorosa que sería la transición de 2 a 3, creo que también habría rehecho mucho más las partes internas del intérprete
    Incluso después de una transición de 12 años, el rendimiento single-thread sigue siendo pésimo, y todavía quedan varias transiciones dolorosas para llegar al multithreading real
    Sé que hay que ser amable con el desarrollo open source, pero me pregunto a partir de qué punto se puede llamar a esto un lenguaje muy mal gestionado

    • No está pésimamente gestionado. Python tiene muchos problemas, pero todos son problemas derivados del éxito de Python
      Las peores partes de Python son las que son difíciles de cambiar porque Python es demasiado popular y su ecosistema es demasiado grande. Por eso, todo tipo de cambio se vuelve más difícil por la compatibilidad hacia atrás
    • Es cierto. Después de tanto tiempo, multiprocessing sigue siendo pésimo
      Hay una tendencia a defender Python demasiado rápido. Es importante verlo objetivamente, sin sesgos
    • En algún momento, ¿no habría que crear un lenguaje con la misma sintaxis que Python, pero diseñado desde cero para tener mejor rendimiento y soporte de threading?
      Los proyectos que quieran rendimiento y sintaxis de Python podrían irse hacia allá. El Python actual parece estar tambaleándose entre varios objetivos sin lograr bien ninguno
    • Era predecible que la transición de 2 a 3 sería larga y mala, y en ese entonces la gente también lo decía
      Perl 5/6 se citaba como ejemplo. Incluso cuando quedó claro que nadie iba a hacer la transición, pasaron unos 5 años más hasta que se intentó hacerla más fácil