15 puntos por darjeeling 2026-03-29 | Aún no hay comentarios. | Compartir por WhatsApp

Logros clave

Plataforma Mejora de rendimiento del JIT (vs. intérprete con tail calling)
macOS AArch64 +11~12%
x86_64 Linux +5~6%

Alcance de los benchmarks: varía desde casos un 20% más lentos hasta casos más de 100% más rápidos (excluyendo el microbenchmark unpack_sequence)

  • Objetivo cumplido: se alcanzó la meta de 3.15 (mejora del 5%) más de un año antes
  • Soporte para free-threading: aún no está terminado; se sigue trabajando con la mira puesta en 3.15/3.16

Lecciones clave

  1. La salida de un sponsor = crisis → transición a un modelo liderado por la comunidad
    Aunque el sponsor principal del equipo Faster CPython se retiró en 2025, las contribuciones voluntarias de la comunidad hicieron que incluso aumentara la cantidad de colaboradores y se lograran resultados.

  2. Divide los problemas complejos en partes pequeñas
    Incluso en proyectos con una barrera de entrada alta como un JIT, descomponer el trabajo en tareas unitarias y ofrecer guías claras permitió que colaboradores sin especialización previa, pero con experiencia en C, pudieran aportar.

  3. Reducir el bus factor es una señal de un proyecto sano
    Los colaboradores de la etapa intermedia (optimizer) pasaron de 2 a 4, y desarrolladores no core también crecieron hasta convertirse en contribuidores clave.

  4. La infraestructura de medición cambia la velocidad del desarrollo
    Un sistema que reporta diariamente el rendimiento del JIT (doesjitgobrrr.com) fue decisivo tanto para detectar regresiones temprano como para mantener la motivación.

  5. El intercambio entre comunidades eleva la capacidad técnica
    La interacción con el equipo de PyPy y las charlas informales con desarrolladores de compiladores se tradujeron en avances técnicos reales.


Contexto y resultados

[IMG] Gráfico de rendimiento del JIT (al 17 de marzo de 2026; cuanto más bajo, más rápido que el intérprete)
(Rendimiento del JIT al 17 de marzo de 2026. Cuanto más bajo, más rápido frente al intérprete. Fuente de la imagen: doesjitgobrrr.com)

Hay buenas noticias. En macOS AArch64, el JIT de CPython alcanzó su objetivo de rendimiento (muy modesto) más de un año antes de lo previsto, y en x86_64 Linux lo logró unos meses antes. El JIT alfa de 3.15 es aproximadamente 11~12% más rápido que el intérprete con tail calling en macOS AArch64, y 5~6% más rápido que el intérprete estándar en x86_64 Linux. Estas cifras son medias geométricas y son preliminares. El rango real es variado: desde casos 20% más lentos hasta casos más de 100% más rápidos (excluyendo el microbenchmark unpack_sequence). Aún no existe un soporte adecuado para free-threading, pero se apunta a tenerlo en 3.15/3.16. El JIT ahora sí ha vuelto a encarrilarse.

Es difícil exagerar lo duro que fue llegar hasta aquí. Hubo un momento en que realmente se dudaba de si el proyecto JIT podría lograr una mejora de velocidad significativa. Mirando hacia atrás, el JIT original de CPython prácticamente no mejoraba el rendimiento. Hace 8 meses publiqué una reflexión sobre el JIT donde señalaba que el JIT original de CPython en 3.13 y 3.14 a menudo era incluso más lento que el intérprete. En ese momento, el equipo Faster CPython también acababa de perder el apoyo de su sponsor principal. Como yo era voluntario, no me afectó de manera directa, pero sí afectó a colegas que trabajaban allí, y durante un tiempo el futuro del JIT parecía incierto.

Entonces, ¿qué cambió respecto a 3.13 y 3.14? No voy a contar una historia heroica de cómo salvamos al JIT del fracaso gracias a nuestro ingenio. Sinceramente creo que gran parte del éxito actual se debe a la suerte. Estar en el momento correcto, en el lugar correcto, con las personas correctas y tomando las decisiones correctas. Pienso seriamente que esto no habría sido posible si faltara aunque fuera una sola de estas personas entre los principales contribuidores del JIT: Savannah Ostrowski, Mark Shannon, Diego Russo, Brandt Bucher y yo. Y para no dejar fuera a otros contribuidores activos del JIT, también quiero mencionar a Hai Zhu, Zheaoli, Tomas Roun, Reiden Ong, Donghee Na, y seguramente también a otras personas que ahora mismo estoy omitiendo.

Quiero hablar sobre las personas y un poco de suerte, dos aspectos del JIT de los que no suele hablarse tanto. Si les interesan los detalles técnicos, pueden verlos aquí.


Parte 1: un JIT impulsado por la comunidad

El equipo Faster CPython perdió a su sponsor principal en 2025. Inmediatamente propuse la idea de una gestión liderada por la comunidad. En ese momento no estaba nada claro que eso pudiera funcionar. El proyecto JIT tiene fama de no ser amigable para nuevos contribuidores. Históricamente exige mucho conocimiento previo especializado.

En el core sprint de CPython celebrado en Cambridge, el equipo core del JIT se reunió y redactó un plan para tener un JIT 5% más rápido para 3.15, y un JIT 10% más rápido junto con soporte para free-threading para 3.16. Hubo algo menos visible pero esencial para la salud del proyecto: reducir el bus factor. Queríamos al menos dos mantenedores activos en cada una de las tres etapas del JIT: el selector de regiones del frontend, el optimizer del middle-end y el generador de código del backend.

Antes solo había 2 contribuidores activos y recurrentes en el middle-end del JIT. Hoy hay 4, y 2 desarrolladores no core (Hai Zhu y Reiden) se convirtieron en miembros capaces y valiosos.

Lo que funcionó para atraer gente fueron prácticas generales de ingeniería de software: dividir los problemas complejos en partes manejables. Brandt empezó a hacerlo primero en 3.14, abriendo varios mega issues que descomponían las optimizaciones del JIT en tareas simples. Por ejemplo, algo como “intenta optimizar una sola instrucción en el JIT”. Yo retomé la idea de Brandt y la apliqué también en 3.15. Por suerte, me tocaron tareas más fáciles: issues para transformar instrucciones del intérprete a una forma fácil de optimizar. Para alentar a nuevos contribuidores, organicé instrucciones muy detalladas para que fueran ejecutables de inmediato. También delimitamos con claridad las unidades de trabajo. Parece que eso ayudó. Incluyéndome a mí, 11 contribuidores trabajaron en ese issue y transformaron casi todo el intérprete a una forma amigable para el optimizer del JIT. La clave fue descomponer el JIT, que antes era un bloque opaco, en algo a lo que pudieran contribuir programadores en C sin experiencia previa en JIT.

Otras tácticas efectivas: animar a la gente y celebrar los logros grandes y pequeños. Todos los PR del JIT tenían entregables claros, y creo que eso le dio dirección a la gente.

El esfuerzo de optimización comunitario dio resultados. El JIT pasó de ser 1% más rápido a ser 3~4% más rápido en x86_64 Linux (ver la línea azul de abajo):

[IMG] Rendimiento del JIT vs. el intérprete durante el período de optimización comunitaria
(Fuente de la imagen: doesjitgobrrr.com)


Parte 2: decisiones afortunadas

Trace Recording

De nuevo, creo que gran parte de esto se debió a la suerte. En el core sprint de CPython en Cambridge, Brandt me convenció de reescribir el frontend del JIT para que usara tracing. Al principio no me gustó la idea, pero por orgullo pensé en reescribirlo “para demostrar que no iba a funcionar”.

El prototipo inicial funcionó en 3 días, pero llevó un mes lograr que realmente hiciera JIT correctamente y pasara la suite de tests. Los resultados iniciales fueron pésimos: era aproximadamente 6% más lento en x86_64 Linux. Justo cuando estaba por rendirme, ocurrió un accidente afortunado: había entendido mal una sugerencia de Mark.

Mark había propuesto conectar con threading la dispatch table al intérprete para tener dos dispatch tables dentro del intérprete: una para el intérprete normal y otra para tracing. Pero yo lo entendí mal e hice una versión más extrema: en vez de tener una versión de tracing para cada instrucción normal, dejé una sola instrucción encargada del tracing y apunté todas las instrucciones de la segunda tabla hacia ella. Resultó ser una decisión muy buena. El enfoque inicial de doble tabla aumentaba al doble el tamaño del intérprete, provocando code bloat y una caída natural del rendimiento, por lo que era mucho más lento. Al usar una sola instrucción y dos tablas, el tamaño del intérprete solo creció en el equivalente a una instrucción, y el intérprete base pudo seguir siendo muy rápido. A este mecanismo lo llamo con cariño dual dispatch.

Un dato que muestra cuán importante fue el trace recording: la cobertura de código del JIT aumentó 50%. Eso significa que, simplificando, todas las optimizaciones futuras habrían sido 50% menos efectivas sin ello.

Agradezco a Brandt y a Mark por haberme llevado, de forma accidental, a descubrir una solución tan buena.

Eliminación del conteo de referencias

Otra decisión afortunada fue intentar la eliminación del conteo de referencias. Esto originalmente fue trabajo de Matt Page en el optimizador de bytecode de CPython. Me di cuenta de que, a pesar del trabajo en el optimizador de bytecode, seguía quedando una rama en el código JIT por cada decremento de conteo de referencias. Pensé: “¿qué pasaría si quitamos esa rama?”, sin tener idea de cuánto ayudaría. Resulta que incluso una sola rama era bastante costosa, y si hay una o más ramas por cada instrucción de Python, terminan acumulándose.

Otro aspecto afortunado fue lo fácil que resultó paralelizar esto, y además terminó siendo una buena herramienta para enseñar a la gente sobre el intérprete y el JIT. Esta fue la optimización principal con la que se asignó trabajo a la gente en el JIT de Python 3.15. Aunque en su mayor parte fue un proceso manual de refactorización, dio a la gente la oportunidad de aprender sin abrumarla con las partes más complejas del JIT.


Parte 3: un gran equipo

Tenemos un gran equipo de infraestructura. En realidad es una sola persona. De hecho, nuestro “equipo” actualmente son 4 máquinas corriendo en el clóset de Savannah. Aun así, Savannah hizo para el JIT el trabajo equivalente al de todo un equipo de infraestructura. Sin una forma de reportar métricas de rendimiento, el JIT no habría podido avanzar tan rápido. Los resultados diarios de ejecución del JIT cambiaron por completo el ciclo de retroalimentación. Ayudaron a detectar regresiones en el rendimiento del JIT y a confirmar que nuestras optimizaciones realmente funcionaban.

Mark es técnicamente brillante. No diré más porque creo que internet ya le ha dado suficientes elogios :).

Diego también es excelente. Está a cargo del JIT en hardware ARM y recientemente empezó a trabajar en hacer que el JIT sea amigable para profilers. Es difícil exagerar lo complicado que es ese problema.

Brandt sentó la base original del backend de código máquina. Sin eso, los nuevos contribuidores habrían tenido que escribir ensamblador, y probablemente eso habría alejado a mucha más gente.


Parte 4: hablar con la gente

Quiero enfatizar el valor de hablar con otras personas y compartir ideas.

Gracias a CF Bolz-Tereick, que me enseñó mucho sobre PyPy. Pasé varios meses revisando el código fuente de PyPy, y creo que eso me convirtió en un mejor desarrollador de JIT en general. CF fue muy amable cada vez que necesité ayuda.

Tengo un chat amistoso sobre compiladores con Max Bernstein, y sin eso probablemente habría perdido la motivación hace mucho tiempo. Max es un autor prolífico y un experto en compiladores muy accesible.

Las ideas no existen en aislamiento. Creo que pasar tiempo con desarrolladores de compiladores me ayudó a volverme más hábil escribiendo JIT. Como mínimo, haber estudiado PyPy amplió mi perspectiva.


Conclusión

La gente importa. Y con un poco de suerte además, JIT go brrr.

Aún no hay comentarios.

Aún no hay comentarios.