1 pontos por GN⁺ 2023-07-30 | 1 comentários | Compartilhar no WhatsApp
  • O Python Steering Council pretende aceitar a PEP 703 para tornar o GIL opcional no CPython, e a reação da comunidade tanto à proposta no-GIL quanto à PEP 703 tem sido, em geral, positiva
  • O objetivo de longo prazo é convergir para um build sem GIL; querem evitar um cenário em que os builds with-GIL e no-GIL, assim como o ecossistema de módulos de extensão, fiquem permanentemente divididos
  • Durante a transição, a compatibilidade retroativa é a principal restrição, e código de terceiros alterado para suportar no-GIL também deve continuar funcionando em builds with-GIL
  • O plano é uma transição em 3 fases: build experimental no curto prazo, build suportado no médio prazo e build padrão no longo prazo; o build experimental pode entrar no Python 3.13, mas não seria um problema se fosse adiado para o 3.14
  • Antes de mudar o build padrão, será preciso confirmar o suporte da comunidade e a experiência com API, empacotamento e distribuição; se a confusão for maior do que os benefícios, será necessário interromper a PEP 703 e buscar outra solução

Direção do Steering Council para aceitar a PEP 703

  • O Python Steering Council tem a intenção de aceitar a PEP 703
  • Na pesquisa sobre a proposta no-GIL, a reação foi amplamente positiva, e o Steering Council também vê de forma geralmente favorável tanto a ideia geral quanto a própria PEP 703
  • No entanto, os detalhes da aceitação ainda estão sendo trabalhados e devem ser finalizados nas próximas semanas

Princípios da transição sem uma bifurcação permanente

  • No longo prazo, provavelmente ao longo de mais de 5 anos, o build no-GIL deverá se tornar o único build
    • Não querem uma situação em que os builds with-GIL e no-GIL permaneçam separados para sempre
    • Também querem evitar que o ecossistema de módulos de extensão fique dividido permanentemente entre os dois builds
  • A compatibilidade retroativa deve ser tratada com muito cuidado
    • Não querem criar outra situação de transição do Python 3
    • Mesmo que código de terceiros seja alterado para suportar o build no-GIL, essas mudanças devem continuar funcionando também no build with-GIL
    • Questões de compatibilidade com versões anteriores do Python devem ser tratadas separadamente
    • Isto não é o Python 4
  • Os requisitos de compatibilidade de ABI, os detalhes dos dois builds e o impacto sobre a compatibilidade retroativa ainda estão em análise

Condições a verificar antes da mudança do padrão

  • Antes de tornar o build no-GIL o padrão, será preciso confirmar se há suporte suficiente da comunidade
  • Não dá para simplesmente mudar o padrão e deixar a comunidade descobrir por conta própria o trabalho necessário
  • Os desenvolvedores centrais precisam experimentar diretamente o novo modo de build e tudo ao seu redor
    • Ao organizar a segurança de threads do código existente, pode ser necessário um novo C API e uma nova Python API
    • Os aprendizados desse processo devem ser compartilhados com a comunidade Python
    • É preciso verificar se as mudanças desejadas pelos desenvolvedores centrais e as mudanças exigidas da comunidade estão em um nível aceitável
  • Até que no-GIL se torne o padrão, deve ser possível mudar de direção se as mudanças causarem confusão demais e poucos benefícios
    • Nesse caso, também deve ser possível decidir reverter todo o trabalho
    • Por isso, código exclusivo para no-GIL deve ser, em certo grau, identificável

Plano de transição em 3 fases

  • No curto prazo, o build no-GIL será adicionado como modo de build experimental
    • Talvez entre no Python 3.13
    • Não seria um problema se fosse adiado para o Python 3.14
    • O status experimental serve para deixar claro que os desenvolvedores centrais apoiam esse modo de build, mas que não se pode esperar suporte imediato da comunidade
    • Será necessário tempo para viabilizar o suporte da comunidade em termos de design de API, empacotamento e distribuição
    • Não é recomendado que distribuições ofereçam o build experimental no-GIL como interpretador padrão
  • No médio prazo, o build no-GIL passará a ser suportado, mas ainda não será o padrão
    • Será necessário ter confiança de que o suporte da comunidade é suficiente para uso em produção
    • Nessa fase, será definida uma data-alvo ou uma versão do Python para tornar no-GIL o padrão
    • O momento dependerá fortemente da compatibilidade retroativa das mudanças de API, do tratamento da stable ABI e do volume de trabalho que a comunidade considerar necessário
    • Isso pode levar pelo menos 1 a 2 anos, ou até mais
    • Depois de declarado como suportado, alguns distribuidores poderão começar a oferecer no-GIL como padrão, mas isso pode depender de quantos pacotes Python suportarem no-GIL naquele momento
  • No longo prazo, a intenção é tornar no-GIL o padrão e remover os vestígios do GIL
    • Não haverá quebra de compatibilidade retroativa sem necessidade
    • Manter por muito tempo dois modos de build comuns pode aumentar a carga sobre a comunidade, como dobrar recursos de teste e cenários de depuração
    • Ao mesmo tempo, não dá para apressar esse processo, e pode levar até 5 anos para chegar a essa fase

Reavaliação contínua e possibilidade de interrupção

  • Ao longo de todo o processo, não só o Steering Council, mas também os desenvolvedores centrais devem reavaliar continuamente o andamento e o cronograma proposto
  • Não querem que esse trabalho se transforme em outra luta de 10 anos por compatibilidade retroativa
  • Se a PEP 703 começar a mostrar sinais de que será problemática, deve ser possível interrompê-la e buscar outra solução
  • Será necessário verificar regularmente se o trabalho em andamento realmente vale a pena

1 comentários

 
GN⁺ 2023-07-30
Comentários do Hacker News
  • Durante décadas, muito código de bibliotecas C vinha com avisos nos manuais dizendo que era instável em contextos assíncronos, reentrantes e recursivos.
    Mesmo assim, aprendemos a lidar com isso, e versões seguras para reentrância foram sendo distribuídas gradualmente sem causar grande instabilidade nas APIs.
    Havia coisas como parsing de strings que tokenizava no próprio buffer, chamadas de DNS que usavam buffers estáticos e código que dependia do comportamento de pilha específico do Vax; na minha visão, o GIL foi ao mesmo tempo uma bênção e uma maldição.

    • Lembro de vasculhar a documentação do runtime C para encontrar funções não reentrantes.
      Acho que isso me criou o hábito de consultar a documentação até de APIs que eu conhecia razoavelmente, por receio de ter perdido algum detalhe importante ou de algo ter mudado.
      Naquela época, eu fazia C++ multiplataforma e, quando vi Java pela primeira vez, ele já trazia desde o início concorrência, garbage collection e vários recursos mais fáceis de usar que em C++; tive certeza de que ele cresceria muito.
      Depois, desenvolvedores mainstream começaram a usar Python; pelo que me lembro, ele era simples por ser originalmente uma linguagem de extensão embutível, e por isso o GIL fazia mais sentido.
    • O modo sem GIL é opcional, e as bibliotecas serão marcadas como “compatíveis com no-GIL”, com o ecossistema passando a dar suporte de forma gradual.
      Não é como se alguém fosse acionar um interruptor e quebrar de uma vez uma enorme quantidade de código C duvidoso.
    • Esse estado não pode durar para sempre.
      Agora já existem CPUs de 128 núcleos, e até CPUs baratas têm 6 núcleos; portanto, a limitação de ficar preso ao desempenho de um único núcleo só tende a pesar mais com o tempo.
    • Quando comecei a trabalhar de verdade com C nos anos 90, também vi avisos desse tipo.
      No começo, acreditei que fossem problemas que se resolveriam em algumas semanas, no máximo alguns meses; eu simplesmente não sabia de nada.
    • É difícil ver equivalência aqui.
      Em C, a interação com funções que não são thread-safe é muito mais direta, e quem usa C geralmente toma mais cuidado.
      Em Python, há módulos C inteiros com estado global; depois de carregar uns 10 deles e somar a complexidade do interpretador, logo ninguém sabe mais o que está acontecendo.
      Mesmo hoje, a maioria dos desenvolvedores, até mesmo desenvolvedores core, não verifica vazamentos de memória; não acho provável que rodem tsan, e, se rodarem, provavelmente será com um conjunto pequeno de testes cobrindo só 10% do código.
      Olhando para as práticas de desenvolvimento de software em Python, especialmente na área de IA, sou muito pessimista quanto a esse recurso.
  • É interessante.
    Python é composto em grande parte por bibliotecas compartilhadas em C escritas com a compreensão de que podiam depender de um bloqueio global.
    Algumas são simples o bastante para funcionar bem sem locks, mas outras ainda precisam de locks e agora serão pressionadas a executar sem o GIL.
    Parte delas provavelmente vai implementar seus próprios locks dentro do seu escopo, e talvez o que realmente faltava ao Python fossem chamadas improvisadas a mutex espalhadas por todo o ecossistema.
    Eu não esperava ver Python ser estragado pela introdução de data races e deadlocks em nome de desempenho.
    Tornar thread-safe uma biblioteca C escrita presumindo um lock global é o tipo de tarefa que até especialistas em concorrência desaconselhariam, e na qual é fácil cometer erros durante a implementação.
    Minha hipótese é que a maioria das pessoas que escreveram extensões C para Python não é especialista em concorrência, mas sim ótimos programadores que não fogem de desafios; com essa combinação, data races/travamentos/segfaults parecem quase inevitáveis.

    • Tornar isso um opt-out explícito como primeira etapa parece razoável.
      Mas a expectativa de transformá-lo em opt-in daqui a 5 anos é otimista demais.
      Todos os desenvolvedores de bibliotecas terão que corrigir suas próprias bibliotecas, inclusive bibliotecas Python, o que é um trabalho difícil; mesmo quando bem-feito, ninguém percebe, então é difícil ser recompensado por isso.
      Muitas bibliotecas nunca tiveram nenhum caso de uso com multiprocessamento, e bibliotecas grandes inevitavelmente terão bugs sutis; reclamações impossíveis de reproduzir e a desistência dos desenvolvedores parecem um resultado garantido.
    • A maioria das bibliotecas C chamadas com frequência a partir de Python também tem bindings para outras linguagens, então imagino que a esta altura já devam ser thread-safe.
      Mesmo Python com GIL dá suporte a threads, portanto essas bibliotecas devem ser pelo menos seguras para reentrância.
      Se uma biblioteca for reentrante, mas não thread-safe, talvez baste adicionar um único lock global envolvendo todas as chamadas, o que é bem parecido com o que o GIL fazia.
      Fazer bibliotecas existentes funcionarem sem o GIL parece relativamente direto em muitos casos, embora o paralelismo possa ser sacrificado.
      O principal problema provavelmente serão bibliotecas que fazem callback do lado C para o runtime do Python.
  • Pergunta ingênua, mas, como existem os pacotes asyncio e multiprocessing, fico pensando quem precisaria de No-GIL
    Nunca tive problemas em Python por causa do GIL; sempre contornei criando um ThreadPool ou ProcessPool, ou usando uma biblioteca assíncrona quando necessário
    Fico curioso se existe algum caso de uso de No-GIL que não seja resolvido com multiprocessing
    Eu achava que a execução em thread única, sem o overhead das ferramentas primitivas de concorrência, era a melhor para computação de alto desempenho. Como o LMAX Disruptor mostrou

    • O ponto central é apenas desempenho
      asyncio é essencialmente single-thread, portanto usa um único core; multiprocessing usa múltiplos cores, então é melhor para desempenho, mas cada processo é relativamente pesado e há overhead adicional de memória compartilhada
      Multithreading baseado em GIL usa um único core e ainda é difícil de usar corretamente
      Multithreading sem GIL usa múltiplos cores, mas é difícil de usar; não sei sobre a implementação, mas a memória compartilhada deveria ser mais rápida que multiprocessing
      Se eu estivesse projetando um sistema novo, concordaria em evitar threads em quase todos os casos de uso de Python e usar asyncio/multiprocessing
      Programas Python que precisam de multithreading rápido muitas vezes nem deveriam ter sido escritos em Python desde o início, mas como já há pessoas que escreveram código CPU-intensivo em Python, No-GIL é algo prático
    • Há muitos casos em que No-GIL não é resolvido por multiprocessing
      Imagine um servidor web que responde simultaneamente a vários clientes usando estado compartilhado; multiprocessing usa pickle para enviar e receber dados, o que tem grande overhead de desempenho
      Por exemplo, se você quiser manter uma estrutura de dados de 1 GB em memória e fazer cálculos paralelos, é difícil obter bom desempenho com multiprocessing
      Usando pickle, nem todos os objetos podem ser compartilhados, e erros de objetos que não podem ser serializados com pickle são muito difíceis de depurar em estruturas de dados complexas
      Em especial, objetos criados por bibliotecas nativas podem não poder ser compartilhados
      Processamentos que precisam compartilhar estado em tempo de execução também são muito difíceis com o módulo multiprocessing; até o exporter Prometheus para Flask precisa de um hack estranho usando um diretório temporário para coletar estatísticas de todos os processos
    • No PEP 703, Manuel Kroiss, da equipe de aprendizado por reforço da DeepMind, explica que o gargalo do GIL leva a reescrever bases de código Python em C++, tornando-as menos acessíveis para pesquisadores
      Em muitas aplicações da DeepMind, eles gostariam de rodar cerca de 50 a 100 threads por processo, mas o GIL frequentemente vira gargalo mesmo com menos de 10
      Como solução de contorno, às vezes usam subprocessos, mas em muitos casos o overhead de comunicação entre processos fica grande demais e eles acabam movendo grandes partes da base de código Python para C++
      Para usos médios, como apps web, multiprocessing pode ser suficiente, mas em cargas de trabalho de IA em grande escala, como as do Google e da DeepMind, o GIL realmente limita o uso de Python
      É por isso que a Meta quer investir três anos-engenheiro nesse trabalho: https://news.ycombinator.com/item?id=36643670
    • Em problemas CPU-bound, asyncio é completamente inútil porque o event loop ainda roda em apenas um core
      Como o nome sugere, ele só ajuda de verdade em problemas I/O-bound
      Compartilhar dados entre vários processos é uma dor enorme, e fazer controle de dados junto com orquestração de processos é uma dor ainda maior
      Processos são caros e, pela dificuldade de compartilhamento de dados mencionada acima, greenlets também dificilmente se tornam uma alternativa real
    • Para uma aplicação web média, não deve ser algo tão revolucionário
      Mas em áreas em que Python tem grande presença, como IA e ciência de dados, poder disparar um monte de threads CPU/GPU-bound e executá-las é uma grande vantagem
  • Muitos extensões C foram escritos sem levar multithreading em conta, então podem surgir problemas — e é bem provável que surjam
    Um pequeno exemplo inseguro se lst puder ser acessado por outra thread está aqui: https://news.ycombinator.com/item?id=36649769
    Mesmo hoje, se código C fizer callback para bytecode Python por meio de um método __del__ e esse bytecode for longo o bastante — talvez cerca de 100 instruções — uma troca de contexto pode acontecer
    Mas é um caso extremamente raro, e muito código de extensão C não foi escrito considerando essa situação
    Pessoas que usam extensões C podem estar dependendo do fato de elas serem executadas atomicamente
    Por exemplo, colocar e retirar arrays numpy em um pool de threads funciona bem hoje, mas pode quebrar sem o GIL

    • Esse tipo de problema realmente será encontrado em grande quantidade e, infelizmente, aparecerá como condições de corrida difíceis de encontrar e depurar
      Por isso a proposta e a direção do trabalho são manter o modo sem GIL totalmente opcional, e não torná-lo padrão
      A minoria corajosa que ativar isso precisa estar preparada para gastar um tempo enorme encontrando e corrigindo condições de corrida sutis em décadas de código de bibliotecas Python
      Os primeiros adotantes vão sofrer bastante ou, mais provavelmente, restringir o uso do modo sem GIL a processos dedicados muito especializados, com dependências reduzidas ao máximo
    • Acho que tudo bem
      É passar de um estado em que se suspeita que há problemas para um estado em que se sabe exatamente onde eles estão; depois é só ir reduzindo essa lista um a um
      Dá para adicionar alguma forma de mutex ao redor do código, ou trocar por uma implementação alternativa não nativa que tenha menos chance de causar problemas
      O argumento contrário parece ser principalmente que dá muito trabalho, não que seja impossível
      Se houver gente suficiente para fazer, resultados podem aparecer
    • “Extremamente raro”, para quem trabalha profissionalmente com runtimes paralelos/assíncronos dando suporte a milhares de servidores em execução contínua, significa que na prática sempre quebra, mas é impossível depurar
    • Acredito que os desenvolvedores centrais do CPython conheçam muito bem esses problemas
      Caso contrário, teriam anunciado um plano de remover o GIL inteiro de uma vez, em vez de uma abordagem gradual em que as pessoas escolhem explicitamente o modo No-GIL
    • Talvez seja um bom momento para criar extensões C com concorrência em primeiro lugar, fáceis de usar, para competir com as extensões existentes
  • Basta lembrar da transição de texto para Unicode, de 32 bits para 64 bits, da migração da Intel para ARM e do Y2K
    No-GIL é uma mudança muito menor e pode seguir o mesmo caminho de transição sem quebrar tudo de forma radical
    Mesmo que algo quebre, haverá uma forma bem definida de lidar com esses casos
    De qualquer maneira, sobrevivemos a essas transições, e é bom ver o avanço
    Isso vai abrir mais áreas que até agora estavam marcadas como impossíveis
    Uma das coisas que o Swift inicial fez bem foi colocar mudanças incompatíveis dentro de uma promessa, e todos sabiam onde estavam e se adaptaram bem
    Às vezes, gostaria que Python seguisse o mesmo caminho

    • Isso é um pouco diferente
      A passagem de 32 bits para 64 bits, para ARM e o Y2K eram coisas cujo funcionamento podia ser testado
      Claro que os testes podem não cobrir casos de falha, mas os testes executados são determinísticos
      Aqui, porém, por mais que se teste, a resposta é “está correto, ou você ainda não atingiu a condição de corrida certa”
    • Não me preocupa a dificuldade da migração, e sim que o estado final possa ser, na prática, pior do que o atual
    • A transição de texto para Unicode foi um problema especialmente grande para Python
  • Eles dizem explicitamente que não querem repetir o cenário da transição para Python 3, mas a abordagem atual parece assustadoramente próxima desse caminho
    Muita coisa depende da comunidade Python e dos canais de distribuição
    A comunidade pode não adotar a tempo, ou distribuições como Ubuntu, Fedora e Anaconda podem se adiantar cedo demais
    Ainda é cedo para afirmar, mas fico em dúvida sobre quanto controle o Steering Council realmente tem para evitar esse tipo de cenário

    • Eles disseram que, cinco anos depois de No-GIL ser disponibilizado, gostariam que esse fosse o único modo de build, mas isso é ao mesmo tempo longo demais e curto demais
      Cinco anos é tempo demais para Python ter dois modos
      Meio decênio é suficiente para que os dois modos se consolidem como o status quo, e posts antigos do Stack Overflow continuarão existindo depois disso
      Não estou otimista de que cinco anos não virem dez anos de incerteza e quebras
      Por outro lado, cinco anos talvez seja pouco demais para desenterrar todo o código C, corrigi-lo, testá-lo e chamá-lo de maduro
    • Vai se parecer com a situação do 2to3
      Empresas que prometeram suporte podem converter mecanicamente alguns projetos e incomodar os desenvolvedores reais, ou ameaçá-los com forks
      Os bugs serão polidos ao longo de anos pelos desenvolvedores reais não remunerados
      Mas Python precisa de algum “sucesso”, e isso vira uma boa frase de divulgação
      No mundo Python, exatidão não parece importar muito
    • Na transição do 2 para o 3, gastaram a carta de uma mudança de versão major que quebrava coisas sem grande motivo, e agora, diante de uma grande mudança, dizem que “não será como o 2 para o 3”
      Por causa desse histórico, soa como se quisessem manter dois modos de execução dentro do CPython 3, em vez de avançar para outra grande transição major
  • É bom que estejam muito conscientes de que isso pode facilmente virar um desastre do Python 4
    É preciso ter extremo cuidado para não afetar acidentalmente o comportamento com GIL
    Se alguma forma de GIL emulado não for exatamente igual ao GIL real, todo tipo de caso estranho será possível

    • Até agora, só vi duas razões para isso ser diferente do 2 -> 3
      Primeiro, eles não querem que seja assim
      Segundo, se for assim, vão desistir rapidamente
      As duas são importantes, mas parece faltar a terceira peça essencial: “e é assim que vamos conseguir isso”
    • Tenho certeza de que a intenção é boa, mas não tenho certeza de que será possível evitar
      Eles já dizem que GIL e No-GIL podem coexistir por mais de 5 anos
      Para criadores de ferramentas, isso significa custo dobrado por pelo menos os próximos 5 anos
      Porque, seja de forma produtiva ou experimental, as pessoas vão querer usar ferramentas nos dois modos
  • GIL significa Global Interpreter Lock
    Há uma boa explicação aqui: https://realpython.com/python-gil/

  • Há dois grandes problemas aqui
    Primeiro, algumas melhorias valem a quebra de compatibilidade retroativa, e remover o GIL é uma dessas melhorias
    É discutível se as mudanças do Python 3 valeram isso, e o fato de print ter virado uma função não parece especialmente valioso
    Ainda assim, a transição do 2 para o 3 foi em parte exagerada por uma minoria barulhenta, e, pela minha experiência migrando mais de 5 bases de código do 2 para o 3, a maioria teve poucos problemas
    O maior problema foi com bases de código que viraram um amontoado de bibliotecas abandonadas porque desenvolvedores anteriores puxaram uma biblioteca para tudo; esse tipo de código tem problemas mesmo que a linguagem central não quebre compatibilidade
    A resposta não é pressionar para que a linguagem nunca mais quebre compatibilidade, mas sim não esperar que trazer o pip inteiro seja uma estratégia sustentável
    Hoje, o Steering Council recebeu críticas demais de uma minoria barulhenta e passou a temer mudanças incompatíveis
    Mas remover o GIL mexe com uma parte tão fundamental de como Python funciona que deveria ser uma mudança incompatível; é melhor admitir isso e planejar a transição do que tentar a tarefa impossível de torná-la não incompatível por medo dos usuários, sem reconhecer os fatos
    Minha base de código também quebrou no Python 3.11, e a correção não foi difícil, mas eu gostaria que tivesse havido uma comunicação melhor de que isso poderia acontecer
    Segundo, o problema mais fundamental é que muitos outros recursos de Python foram construídos em torno do GIL
    Em particular, o paradigma assíncrono faz muito sentido por causa do GIL; sem o GIL, olhando em retrospecto, um modelo de atores no estilo Erlang com send/recv teria sido uma direção muito melhor
    É difícil desfazer isso, e parece pouco demais, tarde demais, como se Python estivesse sendo empurrado para um conjunto menos coeso de recursos que não combinam tão bem entre si

  • Agradeço aos desenvolvedores principais do Python e ao Steering Council; Python é uma das minhas linguagens favoritas, junto com Java e C
    A verdadeira multithreading em Python é muito bem-vinda
    Uso tanto multiprocessing quanto multithreading, dependendo do projeto; há um exemplo de multiprocessing em [0], e um exemplo de uso de Python Threads em tarefas com muita E/S em [1]
    Mas usar threads de verdade seria muito mais eficiente
    Threads podem trocar uma quantidade arbitrária de dados em uma única operação atômica e quase instantânea, mas isso não é possível com a interface de loopback local, multiprocessing ou pipes
    Estou trabalhando em uma arquitetura multithreading que chamo de “three tier multithreading architecture”
    https://github.com/samsquire/three-tier-multithreaded-archit...
    O objetivo é um servidor extremamente escalável e performático, mas Python talvez não seja a ferramenta certa para esse trabalho
    [0]: https://news.ycombinator.com/item?id=36897054 explicação de uso de multiprocessing
    [1]: https://devops-pipeline.com/ exemplo de uso de multithreading