1 pontos por GN⁺ 2023-10-22 | 1 comentários | Compartilhar no WhatsApp
  • O Python steering council declarou a intenção de aprovar a PEP 703, que torna o GIL opcional ao longo de várias releases, e as condições finais ainda estão sendo ajustadas
  • O build --disable-gil do CPython 3.13 será preparado como experimental, e a stable ABI e a compatibilidade de wheels de módulos de extensão surgem como as maiores questões técnicas
  • Wheels abi3 existentes podem não ser diretamente compatíveis com o CPython 3.13 sem GIL, então estão sendo discutidas a introdução de abi4, mudanças na C API limitada e a conversão de macros de contagem de referências em chamadas de função
  • Há preocupação de que o pip possa escolher o wheel errado para builds com GIL e sem GIL; uma instalação silenciosamente incorreta é mais perigosa do que uma simples falha de instalação
  • Para o nome do build sem GIL, foi sugerido free-threading em vez de nogil, mas também é preciso resolver nomes de executáveis, shebangs, instalações paralelas e empacotamento por distribuições

Posição atual da PEP 703 e do CPython sem GIL

  • No fim de julho, o Python steering council declarou a intenção de aprovar a PEP 703, que propõe tornar o global interpreter lock (GIL) opcional no CPython
  • As condições detalhadas da aprovação ainda não foram finalizadas, mas as discussões de implementação relacionadas e a preparação do ecossistema já estão em andamento
  • No longo prazo, presume-se um caminho rumo a uma única versão do CPython sem GIL, mas, por enquanto, a fase é testar o comportamento sem GIL em um interpretador compilado com a opção --disable-gil
  • O CPython 3.13 está previsto para outubro de 2024, e o build sem GIL dessa versão terá caráter experimental

Stable ABI e compatibilidade de módulos de extensão

  • Sam Gross abordou no fórum de discussão do Python como a PEP 703 se relaciona com a stable ABI do CPython
  • A stable ABI serve para permitir que módulos de extensão funcionem em várias versões do CPython com o mesmo wheel binário, sem precisar recompilar a cada nova release do CPython
  • Extensões compiladas para a stable ABI podem não funcionar diretamente no build sem GIL do CPython 3.13
  • Para resolver isso, foram propostas algumas adições e alterações à C API limitada
    • Extensões que usam apenas a C API limitada podem produzir binários que usam a stable ABI
    • As mudanças incluem o plano já existente de converter algumas macros que incrementam e decrementam a contagem de referências de objetos em chamadas de função
  • O objetivo é possibilitar binários de extensão que funcionem tanto no build com GIL quanto no build sem GIL

abi3, abi4 e o problema de seleção de wheels

  • Victor Stinner considera que, para o experimento sem GIL ser bem-sucedido, é necessária uma solução simples para extensões que funcionem nos dois tipos de interpretador
  • Como extensões compiladas para a stable ABI no CPython 3.12 ou anterior não são compatíveis com builds sem GIL a partir da 3.13, surgiu a ideia de criar uma nova versão de ABI, a abi4
    • A stable ABI atual é abi3
    • O número da ABI não precisa estar necessariamente ligado ao número da versão principal do CPython
  • Gross considera aceitável, em certa medida, que extensões que queiram dar suporte ao modo sem GIL tenham o ônus de criar dois wheels binários
    • Ele se preocupa mais com a possibilidade de o projeto sem GIL ficar excessivamente preso a melhorias na C API e na stable ABI
  • Alex Gaynor também mantém vários pacotes de wheels abi3, mas considera que criar dois wheels uma vez não é um fardo excessivamente grande
    • Porém, é importante que o pip, tanto o atual quanto versões futuras, escolha o wheel correto entre os dois
  • Brett Cannon avalia que a lógica atual do pip não distingue as duas versões; portanto, sem uma mudança como abi4, o pip atual e versões antigas não funcionarão corretamente

Preocupação com mau funcionamento silencioso do pip

  • Gross considera que não é preciso se preocupar muito com o suporte a versões antigas do pip no build experimental --disable-gil do CPython 3.13
    • O motivo é que é comum versões antigas do pip quebrarem em novas versões do Python
    • Como exemplo, ele citou o caso em que pip==23.1.1 ou anterior quebra no CPython 3.13 devido à ausência de pkgutil.ImpImporter
  • O mantenedor do pip, Paul Moore, vê quebrar de forma explícita e instalar silenciosamente o pacote errado como problemas diferentes
    • Há usuários que usam versões antigas do pip
    • Uma falha explícita e um erro silencioso têm impactos diferentes sobre o usuário
  • Moore teme que usuários interessados em testar builds sem GIL ou free-threaded fiquem desmotivados se precisarem depurar problemas de compatibilidade de ABI
  • Gaynor também considera que, se o pip se comportar silenciosamente de forma incorreta em pacotes afetados, pode haver uma enxurrada de issues

Instalação paralela e nomes de executáveis

  • Barry Warsaw perguntou se há planos para instalar, no mesmo sistema, builds com GIL e sem GIL lado a lado
  • Gross respondeu que essa situação é semelhante a instalar versões diferentes do Python
  • Cannon considera possível colocar os dois binários em um único wheel “fat”
    • Porém, os nomes dos binários dentro do wheel precisam ser diferentes
  • A discussão sobre nomes de executáveis continuou em uma thread separada
  • Paul Moore considera que os usuários devem conseguir testar facilmente o modo sem GIL e escolher com facilidade entre GIL e sem GIL
    • Se esse processo for difícil em Windows, macOS, Linux etc., isso pode afetar negativamente o projeto sem GIL
    • Os usuários precisam conseguir experimentar com facilidade para que surja demanda por builds sem GIL e, com isso, pressão sobre mantenedores de pacotes para oferecer wheels compatíveis com no-GIL

Debate sobre os nomes nogil e free-threading

  • Barry Scott considera que o nome do executável é importante, pois a linha shebang precisa indicar qual interpretador chamar
    • Como exemplos, sugeriu nomes como python-nogil3 e python-nogil3.13
  • Gregory P. Smith expressou a opinião pessoal de que, como o build sem GIL do CPython 3.13 é um recurso experimental, distribuições não deveriam colocá-lo no $PATH padrão
    • Ele também considera indesejável que nomes longos de executáveis permaneçam por muito tempo em shebangs
    • Sugeriu adiar a decisão sobre o nome de instalação para depois da 3.14
  • Petr Viktorin, desenvolvedor do Fedora, observou que é provável que distribuições queiram empacotar o interpretador sem GIL para permitir experimentos dos usuários
  • Moore considera desejável especificar um build free-threaded em uma forma como #!/usr/bin/env python3.13-nogil
    • A exigência vem da intenção de não codificar diretamente um caminho longo e pouco intuitivo
  • Em uma thread iniciada por Steve Dower sobre o instalador do Windows, Smith afirmou que o steering council quer evitar o nome nogil
    • Os motivos são que ele não comunica bem para a maioria dos desenvolvedores que não são do core, que não é necessário saber o que é o GIL e que o termo inclui uma forma negativa
    • Como alternativa, foi proposto o termo free-threading
  • Gross considera que free-threading também não é fácil para pessoas de fora entenderem e não é um termo amplamente usado
  • Na discussão real, havia forte preferência por nomes curtos, e nogil era o candidato mais forte nesse aspecto
  • A mudança concreta refletida foi trocar a tag de ABI do build sem GIL de n para t
    • t significa threading

Proposta de abi4 e trabalhos restantes

  • Gross e Viktorin discutiram os pontos problemáticos da proposta de mudanças na API, e esse feedback levou à proposta de uma nova ABI, a abi4
  • Gross criou um protótipo da nova ABI
  • Viktorin concorda em linhas gerais com a abordagem, mas considera que os detalhes ainda precisam ser mais refinados
  • Stinner considera que uma PEP para a abi4 é necessária, e Viktorin entende isso como uma discussão pré-PEP
  • Há confusão sobre as garantias de compatibilidade oferecidas pela combinação da versão da C API limitada com abi3, e isso também afeta a direção da abi4
  • A investigação relacionada continua em andamento, e é possível que haja discussões presenciais no core developer sprint em meados de outubro

Texto final de aprovação e impacto de longo prazo

  • O trabalho no CPython sem GIL ou free-threaded continua, mas a aprovação final da PEP 703 ainda está pendente
  • Embora o atraso tenha se alongado um pouco, a PEP 703 e seus efeitos colaterais provavelmente terão grande impacto sobre o desenvolvimento do CPython e seu ecossistema pelos próximos cinco anos ou mais
  • O steering council tenta tornar claros os critérios de aprovação
  • Thomas Wouters afirmou que está refinando o texto exato da aprovação e tentando esclarecer várias decisões
  • Parte do trabalho também poderá ser realizada no core developer sprint

1 comentários

 
GN⁺ 2023-10-22
Opiniões no Hacker News
  • Olhando para os computadores modernos, dá para pensar que paralelismo explícito talvez seja um elemento mais fundamental da ciência da computação do que está na moda nos livros-texto
    Talvez agora estejamos no ponto em que sempre precisamos escrever código paralelo explicitamente

    • Como humanos são ruins em raciocinar sobre várias threads ao mesmo tempo, é provável que a mudança mais prática esteja na direção da sintaxe declarativa, que já aparece
      Por exemplo, loops for estão sendo substituídos por operações como foreach, map e filter. Essas expressões informam ao compilador/interpretador a intenção de aplicar alguma operação a todos os itens de uma estrutura de dados, e deixam para o compilador/runtime decidir se e como paralelizar
    • O paralelismo se dividiu em algumas direções
      Na execução de serviços web, cada requisição é rápida o suficiente, e o verdadeiro ganho do paralelismo está em processar muitas requisições em paralelo. É aí que No-GIL se encaixa
      Quando há muitas sub-requisições dentro de uma única requisição, normalmente isso é tratado com código assíncrono, mas muitas vezes é mais porque criar threads é caro ou porque pools de threads são trabalhosos do que por ganhos de desempenho da assincronicidade. Assíncrono é bom para throughput, mas ruim para latência, e, ao paralelizar requisições de serviço, geralmente a latência preocupa mais. O assíncrono venceu principalmente por usabilidade
      Outro tipo de paralelismo aparece em grandes trabalhos offline. São coisas como MapReduce ou Presto, que em geral se parecem com problemas de dividir para conquistar. O treinamento de modelos em GPU também é parecido com isso
      O que não aconteceu foram algoritmos locais altamente paralelos. Em serviços web, o tamanho dos dados é pequeno, então o ganho de latência é pequeno, a implementação é complexa e o custo de coordenação entre threads aumenta. Uma pequena exceção são os algoritmos vetorizados, mas eles rodam em um único núcleo e não têm overhead de coordenação, e a inferência online também é, de novo, muito fortemente vetorizada
    • Na ciência da computação, paralelismo é um pouco parecido com segurança. Sabemos que é importante em abstrato, mas, para aprender de verdade, é preciso buscar treinamento à parte
      Com o tempo, ambos vêm melhorando. Assim como mais linguagens e bibliotecas ficam seguras por padrão, agora mais coisas são paralelas por padrão. Ainda há um caminho a percorrer, mas acho bom que isso não tenha sido feito cedo demais, porque a tecnologia melhorou muito nos últimos 10 anos
      Por exemplo, dá para comparar o que se pode fazer com segurança com Rayon em Rust e o que se fazia sem segurança com OpenMP em C++
      Mais externamente, também há estas coisas em que trabalho: https://legion.stanford.edu/, https://regent-lang.org/, https://github.com/nv-legate/cunumeric
    • Vejo o paralelismo como algo da mesma categoria que gerenciamento de memória. A maioria dos programas que escrevemos pode, e deve, usar alguma forma de gerenciamento automático, deixando o gerenciamento manual para as áreas em que ele é necessário por desempenho
      Como são detalhes de implementação, se pudermos abstraí-los para usá-los com mais facilidade, devemos fazer isso
    • A wiki do LMAX Disruptor diz que a latência média para enviar uma mensagem de uma thread para outra é de 53 nanossegundos
      Para comparação, um mutex leva cerca de 25 nanossegundos, e aumenta mais quando há contenção, mas mutex é sincronização ponto a ponto
      O bom do Disruptor é que várias threads podem receber a mesma mensagem sem muito esforço adicional
      https://github.com/LMAX-Exchange/disruptor/wiki/Performance-...
      https://gist.github.com/rmacy/2879257
      Sonho com uma linguagem parecida com Smalltalk, mas que permaneça single-thread até que paralelizar faça sentido
      Estou procurando problemas de paralelismo que não sejam big data. Paralelismo é mais parecido com colocar mais carros na estrada do que aumentar a velocidade do carro. Mas ainda estou tentando descobrir o que usuários de desktop ou mobile precisam fazer localmente usando a força matemática do computador
      Também estou pensando em Itanium e na arquitetura VLIW como ideias de paralelismo
  • Basta usar -ng. No sentido de no-gil ou next-generation

    • Isso me lembra a grande onda de suporte a threading em Unix. O que o desenvolvedor precisava fazer variava enormemente de uma plataforma para outra
      Havia novos flags de compilador, novos flags de linker, linkagem com bibliotecas diferentes e até o uso de comandos de compilador completamente diferentes. AIX era especialmente assim
  • Para o problema do shebang, talvez seja melhor se apoiar na convenção existente do Python: from __future__ import nogil
    Nesse ponto, basta fazer hot-swap do interpretador

    • from __future__ import não é uma instrução de runtime, mas uma instrução especial que indica flags
      https://docs.python.org/3/reference/simple_stmts.html#future...
    • “Uma instrução future é uma diretiva para o compilador compilar um módulo específico usando sintaxe ou semântica que estará disponível em uma versão futura do Python em que o recurso se tornará padrão”
      Instruções future são por módulo, e GIL/no-GIL não se encaixa facilmente nesse modelo
    • Se não for executado como o primeiro módulo e o primeiro import, a implementação pode virar um pesadelo
  • Sempre que vejo esta proposta, fico me perguntando como garantir que os programas continuem funcionando corretamente. Uma boa parte do código Python multithread existente foi escrita de forma insegura
    Em especial, o problema são as corridas de dados, que vi repetidamente em bases de código de várias empresas e em projetos open source. Esses programas só não quebram porque dependem implicitamente do fato de que o GIL permite que apenas uma thread execute por vez
    Se o GIL desaparecer, esses programas vão quebrar. Como Python é uma linguagem de tipagem dinâmica, tenho muita dúvida de que exista algum analisador estático capaz de encontrar esses problemas em programas Python existentes
    O mais provável são bugs sutis que aparecem de forma não determinística em runtime. Até seria melhor se dessem crash, mas essa classe de bug tem grande chance de levar a comportamentos incorretos
    Talvez essa proposta sem GIL nem seja para ser usada na maioria dos programas. Pode ser uma ferramenta superespecializada para um conjunto muito pequeno de situações em que o programador sabe que não há GIL e pode escrever o código de acordo

    • Se um programa multithread tem corrida de dados, ele já tem um problema. O GIL não torna corridas de dados impossíveis
      O GIL só significa que apenas uma thread por vez pode executar bytecode Python. Mesmo um interpretador com GIL pode alternar threads entre bytecodes, e muitas operações em Python exigem vários bytecodes. Isso inclui métodos embutidos de tipos embutidos que muita gente considera “atômicos”
      Por isso o Python, mesmo hoje tendo GIL, oferece coisas como locks, mutexes e semáforos
    • Um fato curioso: o GIL definitivamente não impede todos os bugs de condição de corrida
      Threads competindo pelo GIL já podem tomá-lo umas das outras em momentos ruins e causar confusão
    • Entendi que o ponto central é fazer as bibliotecas declararem se dão suporte ao modo nogil, ou seja, ser opt-in
      Se um programa só for executado sem GIL quando todas as dependências permitirem, haverá tempo suficiente para corrigir esses bugs
    • Acho que o Python sem GIL só deve chegar depois de pelo menos 3 ou 4 ciclos de release. Já faz um ano que o 3.11 saiu, e imagino que muito código Python em produção ainda esteja por volta do 3.8
      Então provavelmente só vamos lidar com esse problema em larga escala perto de 2030. Também não se vê muita produção atualizando o runtime em uso diretamente para a release mais recente
      Não quero soar duro, mas o Steering Council disse que não quer outra migração de 2 para 3, então as pessoas não vão atualizar de forma leviana. Muito do que está online hoje pode ser perigoso para copiar e colar
    • O GIL protege apenas o interpretador. O máximo que ele pode fazer é reduzir a frequência dos problemas
      Há muitos bugs de threading em código Python real
  • OCaml não passou por uma evolução parecida? Fico curioso se há pontos comparáveis entre os dois projetos

    • Acho que não. O OCaml 5, em vez de remover um lock global e quebrar código existente, introduziu uma nova primitiva chamada domain, que gerencia uma ou mais threads com um lock compartilhado
      Assim, a API de threads existente cria threads dentro do domain atual e consegue isolar código que espera adquirir um lock. Código novo pode, em vez disso, criar um novo domain que começa com uma thread. Também é possível usar os dois intencionalmente juntos como uma forma de escalonamento
      O Python está tentando tornar o lock completamente opcional de forma global, fora do controle dos autores de bibliotecas. Dito isso, o lock do Python parece ser garantido apenas para proteger o próprio runtime, então a maior parte do código que depende desse lock provavelmente já tem bugs de qualquer forma, e por isso o plano do Python também parece viável
      Se houver algo em comum, talvez seja apenas a necessidade de encontrar e corrigir estados compartilhados inesperados em toda a base de código do runtime e revisar a C ABI
  • Agora o Python também tem a chance de alcançar o Tcl em desempenho multithread: https://www.hammerdb.com/blog/uncategorized/why-tcl-is-700-f...

  • Eu preferiria portar o código Python para Mojo e obter multithreading, SIMD e outros ganhos de velocidade

    • Seria bom se o Mojo fosse um mundo mais completo, mas no momento não chega nem perto disso
    • Concordo. Eu preferiria reescrever em Rust, Nim ou .NET
    • Os downvotes que chegaram a -3 mostram bem que os downvotes no HN não têm a ver com lógica, mas com disputa de território e orgulho
      Não quer migrar código Python para nogil Python? Então toma downvote, é mais ou menos isso
  • Se for para dar um nome, poderia ser algo como python4, python3-gilfoil, python3-gilfree

  • O foco atual em um Python sem GIL me parece bem estranho. A equipe Faster CPython estabeleceu uma meta ambiciosa de aumentar o desempenho do CPython em 50% a cada release
    O 3.11 teve melhorias reais, mas ficou muito aquém dos 50%, e em muitos dos nossos testes o 3.12 foi parecido ou mais lento. Multithreading de verdade seria ótimo, mas eu preferiria muito mais ver primeiro uma melhora no desempenho single-thread
    Claro, reconheço que nossas necessidades podem não representar as de todo mundo, e sou grato por todo o trabalho feito para tornar Python uma ótima linguagem. Ainda assim, fico curioso sobre o que estou deixando passar

    • Acho que Python precisa ter uma resposta urgente para o uso de múltiplos cores. A AMD acabou de lançar uma CPU de 96 cores
      Hoje o uso de múltiplos cores acontece via multiprocessing, que tem muitas limitações. Entendo que múltiplos interpretadores possam vir junto com coisas como corrotinas, mas ainda assim prefiro uma opção real de multithreading
    • São objetivos completamente diferentes. Em teoria, Python multithread pode tornar certos programas mais rápidos, mas o modo como isso acontece importa
      No Python nogil, por exemplo, vários threads podem chamar código C com estado compartilhado acessível como objetos Python. Isso é bem central para machine learning e, na verdade, a forma atual desta PEP veio da equipe do PyTorch
      Desempenho single-thread também é importante, mas para trechos críticos já existiam desvios bem bons, como numba, Cython e Mojo
      A ordem também importa. Se nogil for introduzido, boa parte do trabalho do faster CPython pode acabar sendo totalmente descartada, então as equipes precisaram se coordenar
      Num mundo ideal, teríamos tanto o modo nogil quanto melhorias de desempenho single-thread. Guido também deu a entender que está considerando um JIT sofisticado
    • A parte computacionalmente pesada em “Python” é tratada por bibliotecas como numpy e tensorflow
      Python torna muito conveniente lidar com abstrações de baixo nível em uma linguagem de nível mais alto. Por isso, como desenvolvedor Python de longa data, nunca fiquei muito estressado com o GIL
    • Acho que você deixou passar que as necessidades das pessoas citadas na PEP 703 são diferentes das suas: <https://peps.python.org/pep-0703/#motivation>
    • Pelo que entendo, os dois são projetos separados dentro do CPython e não necessariamente são as mesmas pessoas trabalhando neles
      Se fosse preciso escolher apenas um dos dois, concordo que, para a maioria dos casos de uso, código single-thread simplesmente mais rápido se encaixa melhor. Mas também não há motivo para não ter os dois
  • Em hindsight, é claro, mas acho que, se o lado do Python soubesse o quão longa e dolorosa seria a transição do 2 para o 3, teria feito mudanças muito maiores também no interior do interpretador
    Mesmo depois de uma transição de 12 anos, o desempenho single-thread ainda é péssimo, e ainda restam algumas transições dolorosas até chegar a multithreading de verdade
    Sei que devemos ser gentis com o desenvolvimento open source, mas me pergunto a partir de que ponto podemos chamar uma linguagem de muito mal administrada

    • Não é mal administrada. Python tem muitos problemas, mas todos são problemas que vêm do sucesso do Python
      As piores partes do Python são aquelas difíceis de mudar porque Python é popular demais e seu ecossistema é grande demais. Por isso, todo tipo de mudança fica mais difícil por causa da retrocompatibilidade
    • É verdade. Mesmo depois de todo esse tempo, multiprocessing ainda é péssimo
      Há uma tendência a defender Python rápido demais. É importante olhar objetivamente, sem viés
    • Em algum momento, acho que talvez fosse preciso criar uma linguagem com a mesma sintaxe do Python, mas projetada desde o início para ter desempenho melhor e suporte melhor a threads
      Projetos que queiram desempenho e sintaxe Python poderiam ir para ela. O Python atual parece estar se debatendo entre vários objetivos e não alcançando nenhum deles direito
    • Era previsível que a transição do 2 para o 3 seria longa e ruim, e as pessoas diziam isso na época
      Perl 5/6 era citado como exemplo. Mesmo quando ficou claro que ninguém estava migrando, ainda levou cerca de mais 5 anos até tentarem tornar a transição mais fácil