- O projeto CPython introduziu recentemente uma nova estratégia de implementação para o interpretador de bytecode. Os resultados iniciais mostraram um ganho médio de desempenho de 10–15% em várias plataformas
- No entanto, esse ganho de desempenho foi em grande parte resultado de contornar um problema de regressão no LLVM 19. Quando comparado com referências melhores (por exemplo, GCC, clang-18, LLVM 19 com flags de ajuste específicas), o ganho cai para 1–5%
Resultados de desempenho
- Foram feitos benchmarks de várias builds do interpretador CPython usando diferentes compiladores e opções de configuração. Os testes foram realizados em um servidor Intel e em um Apple M1 Macbook Air.
- Todas as builds usaram LTO e PGO. Foi usada a média reportada por
pypeformance/pyperf compare_to, tomando clang18 como referência.
- Comparação de desempenho dos compiladores
- Apple M1 Macbook Air :
- clang18: referência
- clang19: 1,12x mais lento
- clang19.taildup: 1,02x mais lento
- clang19.tc: 1,00x mais lento
- gcc: N/A
- O interpretador com tail-call ainda mostrou ganho de velocidade em relação ao clang-18, mas a queda de desempenho ao migrar para o clang-19 foi ainda mais dramática.
Regressão no LLVM
Contexto rápido
- Interpretadores tradicionais de bytecode consistem em uma instrução
switch dentro de um loop while. A maioria dos compiladores compila switch como uma tabela de saltos.
- Compiladores C modernos oferecem suporte ao padrão de obter o endereço de labels e usá-lo como um "computed goto". O CPython usava esse padrão até o trabalho com tail-call.
Regressão no LLVM 19
- O LLVM 19 impôs limites ao passe de tail-duplication, fazendo com que a duplicação fosse interrompida quando o tamanho do IR ultrapassasse um certo limite. Com isso, no CPython todos os saltos de dispatch foram mesclados, anulando completamente o propósito da implementação baseada em
goto calculado.
Outras anomalias
- Há confiança de que a mudança na lógica de duplicação de tail-call causou a regressão, mas isso não explica completamente a magnitude da regressão.
- Em processadores modernos, ganhos de velocidade de 2–4% são mais comuns.
O computed goto é necessário?
- O benchmark
clang19.nocg afirma ser mais rápido que clang19. Isso mostra que o compilador pode aplicar as mesmas otimizações usando um interpretador baseado em switch.
Correção
- O pull request 114990 do LLVM corrigiu a regressão. Essa correção restaabelece o desempenho esperado.
Reflexões
Sobre benchmarking
- Ao otimizar sistemas, são definidos benchmarks e metodologias de benchmarking, e as mudanças propostas são avaliadas com base neles.
- Benchmarks exigem mais suposições e confiança para generalizar um ponto de dados específico.
Linha de base
- Ao propor uma nova solução ou método, é comum comparar com a "melhor abordagem atualmente conhecida".
Sobre engenharia de software
- Sistemas de software são complexos, interconectados e mudam rapidamente.
- Compiladores otimizadores vivem uma tensão entre respeitar a intenção do programador e ainda assim otimizar o código.
Compiladores otimizadores
- O atributo
musttail representa um novo tipo de recurso de compilador relacionado à otimização. Isso pode oferecer um estilo mais poderoso para escrever código sensível a desempenho.
Mais uma observação sobre nix
- O
nix foi muito útil neste projeto. Ele ajudou bastante a gerenciar e compilar várias versões do interpretador Python.
1 comentários
Comentários do Hacker News
Olá. Sou o autor do PR que introduziu o interpretador com tail-calling no CPython
Fazer benchmarking direito é realmente muito difícil
Parabéns ao autor por ter investigado a fundo a verdade por trás desse problema
Este é um bom exemplo de que C não é uma linguagem "próxima da máquina"
switch()"ingênuo"Ao ajustar a forma como o compilador organiza o loop, o interpretador com tail-call não é tão eficaz quanto foi anunciado
Avaliar o desempenho de builds do Python é muito difícil
Discussões relacionadas:
Excelente artigo
Recentemente, fiz benchmarking do Python 3.9 ao 3.13
Fico me perguntando como esse tipo de otimização se relaciona com tail-call optimization