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
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 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.
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 executargetaddrinfo(3).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=ndefine o número máximo de threads nativas. O valor padrão é 8.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.