1 pontos por GN⁺ 2023-12-26 | 1 comentários | Compartilhar no WhatsApp

Lançamento do Ruby 3.3.0

  • A versão Ruby 3.3.0 foi lançada. Ela traz o novo parser Prism, usa o Lrama como gerador de parser, adiciona o compilador JIT RJIT escrito em Ruby puro e, em especial, melhora o desempenho do YJIT.

Parser Prism

  • Prism é um parser recursivo descendente para a linguagem Ruby, portátil, resistente a erros e fácil de manter, fornecido como gem padrão.
  • Prism é adequado para uso em produção, recebe manutenção ativa e pode ser usado como substituto do Ripper.
  • Há documentação detalhada sobre como usar o Prism.
  • Prism é uma biblioteca em C usada internamente pelo CRuby e também um gem Ruby que pode ser usado por qualquer ferramenta que precise fazer parsing de código Ruby.
  • Entre os principais métodos da API do Prism estão Prism.parse(source), Prism.parse_comments(source) e Prism.parse_success?(source).
  • É possível contribuir diretamente no repositório do Prism enviando pull requests ou issues.
  • Para usar experimentalmente o compilador Prism, é possível usar ruby --parser=prism ou RUBYOPT="--parser=prism", mas isso deve ser feito apenas para fins de depuração.

Gerador de parser Lrama

  • O Bison foi substituído pelo gerador de parser LALR Lrama.
  • Quem tiver interesse pode consultar a visão futura para o parser do Ruby.
  • Para facilitar a manutenção, o parser interno do Lrama foi substituído por um parser LR gerado pelo Racc.
  • Há suporte a regras parametrizadas (?, *, +), que serão usadas no parse.y do Ruby.

YJIT

  • Há melhorias significativas de desempenho em relação ao Ruby 3.2.
  • O suporte a argumentos splat e rest foi melhorado.
  • Registradores agora são alocados para operações de pilha da máquina virtual.
  • Mais chamadas com argumentos opcionais passam a ser compiladas. Manipuladores de exceção também são compilados.
  • Tipos de chamada não suportados e call sites megamórficos não voltam mais para o interpretador.
  • Métodos básicos como #blank? do Rails e o caso especial de #present? passam a ser tratados inline.
  • Integer#*, Integer#!=, String#!=, String#getbyte, Kernel#block_given?, Kernel#is_a?, Kernel#instance_of?, Module#=== e outros foram especialmente otimizados.
  • A velocidade de compilação ficou um pouco mais rápida do que no Ruby 3.2.
  • No Optcarrot, ficou mais de 3 vezes mais rápido que o interpretador!
  • O uso de memória melhorou bastante em relação ao Ruby 3.2.
  • Os metadados do código compilado passam a usar muito menos memória.
  • --yjit-call-threshold sobe automaticamente de 30 para 120 em aplicações com mais de 40.000 ISEQ.
  • Foi adicionado --yjit-cold-threshold, que pula a compilação de ISEQ frios.
  • Em Arm64, é gerado código mais compacto.
  • O code GC fica desativado por padrão.
  • --yjit-exec-mem-size passa a ser tratado como um limite rígido a partir do qual a compilação de novo código é interrompida.
  • Não há perda de desempenho causada por code GC, e há melhor comportamento de copy-on-write quando o servidor faz refork usando Pitchfork.
  • Se desejado, é possível ativar o code GC com --yjit-code-gc.
  • Foi adicionado RubyVM::YJIT.enable, permitindo ativar o YJIT em tempo de execução.
  • O Rails 7.2 pretende usar esse método para ativar o YJIT por padrão.
  • Esse método pode ser usado para ativar o YJIT apenas depois que a aplicação terminar o boot.
  • Para manter o YJIT desativado na inicialização enquanto usa outras opções do YJIT, é possível usar --yjit-disable.
  • Mais estatísticas do YJIT são fornecidas por padrão.
  • yjit_alloc_size e várias estatísticas relacionadas a metadados passam a ser fornecidas por padrão.
  • A estatística ratio_in_yjit, gerada por --yjit-stats, fica disponível em builds de release. Não são mais necessários builds especiais de estatísticas ou de desenvolvimento.
  • Foram adicionados mais recursos de profiling.
  • --yjit-perf foi adicionado para facilitar profiling com o Linux perf.
  • --yjit-trace-exits passa a oferecer suporte a amostragem com --yjit-trace-exits-sample-rate=N.
  • Também houve testes mais abrangentes e várias correções de bugs.

RJIT

  • O compilador JIT RJIT, escrito em Ruby puro, foi introduzido para substituir o MJIT.
  • O RJIT é suportado apenas em arquitetura x86-64 em plataformas Unix.
  • Diferentemente do MJIT, ele não requer um compilador C em tempo de execução.
  • O RJIT existe apenas para fins experimentais.
  • Em produção, deve-se continuar usando o YJIT.
  • Se houver interesse no desenvolvimento do JIT do Ruby, recomenda-se ver a apresentação de k0kubun no terceiro dia da RubyKaigi.

Escalonador de threads M:N

  • Foi introduzido um escalonador de threads M:N.
  • M threads Ruby passam a ser gerenciadas por N threads nativas (threads do sistema operacional), reduzindo o custo de criação e gerenciamento de threads.
  • O escalonador de threads M:N pode quebrar a compatibilidade com extensões C, então fica desativado por padrão no Ractor principal.
  • É possível ativar threads M:N no Ractor principal usando a variável de ambiente RUBY_MN_THREADS=1.
  • Em Ractors não principais, as threads M:N ficam sempre ativadas.
  • A variável de ambiente RUBY_MAX_CPU=n define o número máximo de N (quantidade máxima de threads nativas). O padrão é 8.
  • Como apenas uma thread Ruby pode rodar por Ractor, aplicações com um único Ractor (a maioria das aplicações) usam apenas 1 thread nativa.
  • Mais threads nativas do que N podem ser usadas para dar suporte a operações bloqueantes.

Melhorias de desempenho

  • defined?(@ivar) foi otimizado usando Object Shapes.
  • Resolução de nomes como Socket.getaddrinfo agora pode ser interrompida (em ambientes compatíveis com pthreads).
  • Há várias melhorias de desempenho no garbage collector.
    • Objetos jovens referenciados por objetos antigos não são mais promovidos imediatamente para a geração antiga, reduzindo bastante a frequência de coletas GC maiores.
    • Foi introduzida a nova variável de ajuste REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO, que controla a quantidade de objetos não protegidos que dispara uma coleta GC maior. O valor padrão foi definido como 0,01 (1%), reduzindo bastante a frequência de coletas GC maiores.
    • Write Barriers foram implementadas para muitos tipos centrais que não tinham isso. Como resultado, o tempo de pequenas coletas GC e a frequência de coletas GC maiores diminuem bastante.
    • A maioria das classes centrais agora usa Variable Width Allocation. Isso torna a alocação e desalocação dessas classes mais rápidas, reduz o uso de memória e diminui a fragmentação do heap.
    • Foi adicionado suporte a weak references no garbage collector.

Outras mudanças notáveis

  • O IRB recebeu várias melhorias, incluindo integração avançada com irb:rdbg, suporte a pager para os comandos ls, show_source e show_cmds, maior precisão e utilidade das informações fornecidas pelos comandos ls e show_source, além de autocompletar experimental com uso de análise de tipos.
  • O IRB também passou por uma ampla refatoração para facilitar melhorias futuras e recebeu dezenas de correções de bugs.

Problemas de compatibilidade

  • Chamar it dentro de um bloco sem argumentos não é mais recomendado, e no Ruby 3.4 passará a referenciar o primeiro parâmetro do bloco.
  • Variáveis de ambiente obsoletas foram removidas.

Atualizações da biblioteca padrão

  • RubyGems e Bundler exibem um aviso quando o usuário requer os gems a seguir sem adicioná-los ao Gemfile ou ao gemspec. Isso acontece porque esses gems passarão a ser bundled gems em versões futuras do Ruby.
  • Foram adicionados ou atualizados vários gems padrão, como prism 0.19.0, RubyGems 3.5.3, abbrev 0.1.2 e muitos outros.
  • Vários bundled gems foram promovidos de gems padrão ou atualizados, como racc 1.7.3, minitest 5.20.0 e muitos outros.

Opinião do GN⁺

  • Introdução do parser Prism: uma das características mais importantes do Ruby 3.3.0 é a introdução do novo parser Prism. Isso deve ajudar bastante desenvolvedores Ruby ao oferecer um parser mais eficiente para código Ruby, resistente a erros e fácil de manter.
  • Melhorias de desempenho do YJIT: as principais melhorias de desempenho do YJIT devem aumentar significativamente a velocidade de execução de aplicações Ruby, e especialmente a redução no uso de memória e as otimizações de GC tendem a impactar positivamente o desempenho e a estabilidade de grandes aplicações Ruby.
  • Escalonador de threads M:N: a introdução do escalonador de threads M:N tem potencial para melhorar o desempenho de aplicações Ruby multithread. Isso deve reduzir o custo de gerenciamento de threads e permitir processamento paralelo mais eficiente.

1 comentários

 
GN⁺ 2023-12-26
Opiniões do Hacker News
  • Com a chegada do Ruby 3.3, o Ruby — uma linguagem que prioriza a felicidade do desenvolvedor — deixa para trás a antiga imagem de lentidão e agora exibe alta velocidade.

    • O desempenho do Ruby melhorou muito graças a inovações como a tecnologia YJIT, formas de objeto e otimizações de GC.
    • Grandes empresas usuárias de Ruby, como a Shopify, estão vivenciando as melhorias de desempenho do Ruby 3.3.
    • Há grande expectativa pessoal em relação ao futuro do Ruby, além de entusiasmo para aplicar o Ruby 3.3 em sites de produção de clientes.
  • O Ruby 3.3 é o lançamento mais importante e mais rico em recursos dos últimos 10 anos, e há surpresa pelo fato de ter lançado JIT antes do Python.

    • Recursos variados como Prism, Lrama e IRB foram discutidos em uma submissão anterior no Hacker News.
    • Funcionalidades como Ractor, escalonador de threads M:N, Fibre e Async não foram suficientemente mencionadas no contexto do Rails, e há interesse em ouvir relatos de quem usa esses recursos em produção.
  • Foi informado que o Ruby 3.3 já pode ser usado no Heroku.

  • Todo Natal, a linguagem Ruby lança uma nova versão.

  • Foi feita a pergunta se vale a pena aprender Ruby caso a pessoa já conheça Python e NodeJS. O Ruby parece atraente, mas também difícil.

  • A resolução de nomes, como em Socket.getaddrinfo, pode bloquear. Sempre que a resolução de nomes é necessária, uma pthread de worker é criada para executar getaddrinfo(3).

    • Foi perguntado se outros runtimes de linguagem fazem algo semelhante. A criação de threads pode parecer custosa, mas segundo benchmarks, o overhead foi minimizado.
  • O Prism parece interessante. Foi perguntado se há exemplos de uso do Prism como ferramenta de análise de código Ruby.

  • A variável de ambiente RUBY_MAX_CPU=n define o número máximo de threads nativas. O valor padrão é 8.

    • Foi levantada a dúvida se o valor padrão não deveria ser igual ao número de núcleos lógicos, como no Tokio do Rust e em muitos outros runtimes M:N.
  • Há procura por bons exemplos usando Prism. Houve decepção por não encontrar muita coisa além de "APIs notáveis" na página de lançamento.

  • Foi mencionado que este é o presente de Natal perfeito.