15 pontos por darjeeling 2026-03-29 | Ainda não há comentários. | Compartilhar no WhatsApp

Principais resultados

Plataforma Ganho de desempenho do JIT (vs interpretador com tail calling)
macOS AArch64 +11~12%
x86_64 Linux +5~6%

Faixa dos benchmarks: varia de casos 20% mais lentos até casos mais de 100% mais rápidos (excluindo o microbenchmark unpack_sequence)

  • Meta atingida: a meta da 3.15 (melhoria de 5%) foi alcançada mais de 1 ano antes
  • Suporte a free-threading: ainda não está concluído, com trabalho em andamento mirando 3.15/3.16

Principais lições

  1. Perda de patrocinador = crise → transição para liderança da comunidade
    Mesmo após a saída do principal patrocinador da equipe Faster CPython em 2025, contribuições voluntárias da comunidade fizeram o número de contribuidores crescer e gerar resultados.

  2. Divida problemas complexos em partes menores
    Até projetos com alta barreira de entrada, como um JIT, puderam receber contribuições de não especialistas com experiência em C ao decompor o trabalho em tarefas menores e fornecer guias claros.

  3. Reduzir o bus factor é um indicador de projeto saudável
    O número de contribuidores do estágio intermediário (optimizer) cresceu de 2 para 4, e até desenvolvedores não core evoluíram para contribuidores essenciais.

  4. Infraestrutura de medição muda a velocidade de desenvolvimento
    Um sistema que reporta diariamente o desempenho do JIT (doesjitgobrrr.com) foi decisivo tanto para detectar regressões cedo quanto para manter a motivação.

  5. Intercâmbio entre comunidades eleva a capacidade técnica
    A troca com a equipe do PyPy e conversas informais com desenvolvedores de compiladores levaram a avanços técnicos concretos.


Contexto e resultados

[IMG] Gráfico de desempenho do JIT (em 17 de março de 2026; quanto menor, mais rápido que o interpretador)
(Desempenho do JIT em 17 de março de 2026. Quanto menor, mais rápido em relação ao interpretador. Fonte da imagem: doesjitgobrrr.com)

Boas notícias. No macOS AArch64, a meta de desempenho (bastante modesta) do JIT do CPython foi alcançada mais de 1 ano antes do previsto e, no x86_64 Linux, alguns meses antes. O JIT alpha da 3.15 é cerca de 11~12% mais rápido que o interpretador com tail calling no macOS AArch64 e 5~6% mais rápido que o interpretador padrão no x86_64 Linux. Esses números são médias geométricas e ainda preliminares. Na prática, a faixa varia de casos 20% mais lentos até casos mais de 100% mais rápidos (excluindo o microbenchmark unpack_sequence). Ainda não há suporte adequado a free-threading, mas esse é o objetivo para a 3.15/3.16. O JIT agora voltou aos trilhos.

É impossível exagerar o quanto isso foi difícil. Houve um momento em que existia uma dúvida séria sobre se o projeto do JIT conseguiria atingir ganhos reais de velocidade. Olhando para trás, o JIT original do CPython praticamente não trazia ganho de desempenho. Há 8 meses, publiquei um texto de retrospectiva sobre o JIT mostrando que o JIT original do CPython nas versões 3.13 e 3.14 frequentemente era mais lento que o interpretador. Naquele momento, a equipe Faster CPython também havia perdido o apoio de seu principal patrocinador. Como voluntário, isso não me afetou diretamente, mas afetou colegas que trabalhavam nisso, e por um tempo o futuro do JIT pareceu incerto.

Então, o que mudou em relação à 3.13 e à 3.14? Não vou contar uma história heroica dizendo que salvamos o JIT do fracasso por pura genialidade. Honestamente, acho que grande parte do sucesso atual se deve à sorte. A hora certa, o lugar certo, as pessoas certas, as escolhas certas. Sinceramente, acho que isso não teria sido possível se faltasse qualquer um entre os principais contribuidores do JIT: Savannah Ostrowski, Mark Shannon, Diego Russo, Brandt Bucher e eu. Para não deixar de fora outros contribuidores ativos do JIT, vou citar mais alguns nomes: Hai Zhu, Zheaoli, Tomas Roun, Reiden Ong, Donghee Na, e certamente há outros que posso estar esquecendo.

Quero falar sobre a parte pouco mencionada do JIT: as pessoas e um pouco de sorte. Se você quiser os detalhes técnicos, veja aqui.


Parte 1: JIT liderado pela comunidade

A equipe Faster CPython perdeu seu principal patrocinador em 2025. Eu imediatamente propus a ideia de gestão liderada pela comunidade. Na época, era bastante incerto se isso daria certo. O projeto do JIT é conhecido por não ser amigável para novos contribuidores. Historicamente, ele exige bastante conhecimento prévio especializado.

No core sprint do CPython realizado em Cambridge, a equipe central do JIT se reuniu e elaborou um plano para ter um JIT 5% mais rápido até a 3.15 e um JIT 10% mais rápido, com suporte a free-threading, até a 3.16. Houve também um ponto menos chamativo, mas essencial para a saúde do projeto: reduzir o bus factor. Queríamos mais de 2 mantenedores ativos em cada uma das três etapas do JIT: o seletor de regiões no frontend, o optimizer no middle-end e o gerador de código no backend.

Antes, o middle-end do JIT tinha apenas 2 contribuidores recorrentes ativos. Hoje, ele tem 4 contribuidores recorrentes ativos, e 2 desenvolvedores não core (Hai Zhu e Reiden) cresceram e se tornaram membros competentes e valiosos.

O que funcionou bem para atrair pessoas foram práticas comuns de engenharia de software: quebrar problemas complexos em partes gerenciáveis. Brandt começou isso primeiro na 3.14, abrindo várias mega issues que dividiam otimizações do JIT em tarefas simples. Algo como “tente otimizar uma única instrução no JIT”. Eu segui a ideia do Brandt e a apliquei também na 3.15. Felizmente, fiquei com a parte mais fácil: issues que transformavam instruções do interpretador em formas fáceis de otimizar. Para incentivar novos contribuidores, organizei instruções bem detalhadas para que pudessem ser executadas imediatamente. Também delimitei claramente as unidades de trabalho. Isso parece ter ajudado. Incluindo eu, 11 contribuidores trabalharam nessa issue e transformaram quase todo o interpretador em uma forma amigável ao optimizer do JIT. O ponto central foi decompor o JIT de um bloco opaco em algo ao qual programadores C sem experiência prévia com JIT pudessem contribuir.

Outras abordagens eficazes: encorajar as pessoas e celebrar conquistas grandes e pequenas. Todo PR do JIT tinha um resultado claro, e acho que isso deu às pessoas um senso de direção.

O esforço de otimização da comunidade deu resultado. O JIT passou de 1% mais rápido para 3~4% mais rápido no x86_64 Linux (veja a linha azul abaixo):

[IMG] Desempenho do JIT vs interpretador durante o período de otimização pela comunidade
(Fonte da imagem: doesjitgobrrr.com)


Parte 2: Escolhas afortunadas

Trace Recording

De novo, acho que grande parte disso se deve à sorte. No core sprint do CPython em Cambridge, Brandt me convenceu a reescrever o frontend do JIT com uma abordagem de tracing. No começo eu não gostei da ideia, mas por teimosia pensei: vamos reescrever “para provar que não funciona”.

O protótipo inicial começou a funcionar em 3 dias, mas levou um mês para realmente fazer JIT de forma adequada enquanto passava pela suíte de testes. Os resultados iniciais foram péssimos. No x86_64 Linux, ele era cerca de 6% mais lento. Quando eu estava prestes a desistir, aconteceu um acidente de sorte: eu tinha entendido errado uma sugestão do Mark.

Mark havia sugerido conectar uma dispatch table por threading ao interpretador, para que o interpretador tivesse duas dispatch tables (uma para o interpretador normal e outra para tracing). Mas eu entendi errado e fiz uma versão mais radical: em vez de uma versão de tracing para cada instrução normal, deixei apenas uma instrução responsável pelo tracing, e todas as instruções da segunda tabela apontavam para ela. Isso acabou sendo uma ótima escolha. A abordagem inicial com tabela dupla deixava o interpretador duas vezes maior, causando code bloat e perda natural de desempenho, o que a tornava muito mais lenta. Ao usar apenas uma única instrução e duas tabelas, aumentamos o tamanho do interpretador em apenas uma instrução e mantivemos o interpretador base muito rápido. Chamamos esse mecanismo, com carinho, de dual dispatch.

Um número que mostra como o trace recording foi importante: a cobertura de código do JIT aumentou 50%. Isso significa que todas as otimizações futuras teriam sido (simplificando) 50% menos eficazes sem isso.

Sou grato ao Brandt e ao Mark por terem nos levado a descobrir essa solução incrível por acaso.

Eliminação de contagem de referência (Reference Count Elimination)

Outra escolha afortunada foi tentar eliminar contagem de referência. Esse era originalmente um trabalho que Matt Page havia feito no otimizador de bytecode do CPython. Notei que, apesar desse trabalho no otimizador de bytecode, ainda restava um branch no código gerado por JIT para cada decremento de contagem de referência. Pensei: “e se eu tentar remover esse branch?”. Eu não fazia ideia do quanto isso ajudaria. Um único branch já pode ser relativamente caro, e quando há 1 ou mais branches por instrução Python, isso vai se acumulando.

Outro ponto de sorte foi como isso era fácil de paralelizar e como serviu como ótima ferramenta para ensinar às pessoas sobre o interpretador e o JIT. Essa foi a principal otimização em que orientei as pessoas a trabalhar no JIT do Python 3.15. Em grande parte foi um processo manual de refatoração, mas também deu às pessoas a chance de aprender sem serem sobrecarregadas pelas partes mais profundas do JIT.


Parte 3: Uma equipe excelente

Temos uma ótima equipe de infraestrutura. Na verdade, é uma pessoa só. Na prática, nossa “equipe” atualmente são 4 máquinas rodando no armário da Savannah. Ainda assim, Savannah fez, para o JIT, o trabalho equivalente ao de uma equipe inteira de infraestrutura. Sem uma forma de reportar números de desempenho, o JIT não teria avançado tão rápido. Os resultados diários das execuções do JIT mudaram completamente o ciclo de feedback. Eles ajudaram a capturar regressões no desempenho do JIT e permitiram confirmar que nossas otimizações estavam realmente funcionando.

Mark é tecnicamente brilhante. Não vou falar mais porque acho que a internet já lhe dá elogios demais :).

Diego também é excelente. Ele cuida do JIT em hardware ARM e, recentemente, começou a trabalhar para tornar o JIT amigável a profilers. É impossível exagerar o quão difícil esse problema é.

Brandt construiu a base original do backend de código de máquina. Sem isso, novos contribuidores teriam precisado escrever assembly, o que provavelmente teria afastado ainda mais gente.


Parte 4: Conversar com outras pessoas

Quero enfatizar o valor de conversar com outras pessoas e trocar ideias.

Agradeço a CF Bolz-Tereick, que me ensinou muito sobre PyPy. Passei meses olhando o código-fonte do PyPy, e acho que isso me tornou um desenvolvedor de JIT melhor de forma geral. CF foi muito gentil em ajudar quando precisei.

Participo de um chat informal sobre compiladores com Max Bernstein, e sem isso eu provavelmente teria perdido a motivação há muito tempo. Max é um autor prolífico e um especialista em compiladores muito acessível.

Ideias não existem em isolamento. Acho que passei a escrever JITs com mais habilidade por ter convivido por algum tempo com desenvolvedores de compiladores. No mínimo, explorar o PyPy ampliou minha visão!


Conclusão

Pessoas importam. E, com um pouco de sorte a mais, JIT go brrr.

Ainda não há comentários.

Ainda não há comentários.