githublinkedinemail
← /articles

Entender o runtime do Ruby (não só a linguagem)

A maioria conhece a linguagem. Quase nenhum conhece o runtime. YARV, heap, GC, GVL, inline cache — onde a engenharia mora e onde os bugs de produção nascem.

4 minruby

Entender o runtime do Ruby (não só a linguagem)

A maioria dos devs Ruby conhece a linguagem.

Quase nenhum conhece o runtime.

E essa é a diferença entre quem escreve código que funciona e quem entende por que ele funciona.


Linguagem ≠ Runtime

A linguagem é a sintaxe.

class User
  def initialize(name)
    @name = name
  end
end

O runtime é tudo o que acontece quando essa classe vira algo executando na memória.

Coisas como:

  • método sendo resolvido
  • objeto sendo alocado
  • GC marcando referência
  • thread pegando o GVL
  • bytecode rodando na VM

Linguagem você lê. Runtime você sente.

Principalmente quando trava.


O Ruby que você usa é o MRI (CRuby)

Quando você roda ruby, está rodando MRI — também chamado de CRuby.

É um runtime escrito em C que faz:

Source (.rb)
   ↓
Parser → AST
   ↓
Compiler → YARV bytecode
   ↓
VM executando bytecode

YARV = Yet Another Ruby VM.

Cada método seu vira uma sequência de instruções de bytecode. Tipo:

trace        1
putself
putobject    "hello"
opt_send_without_block <calldata!mid:puts>
leave

Você nunca precisa olhar isso.

Mas quando algo está lento, é aí que o tempo está sendo gasto.


A heap do Ruby

Todo objeto Ruby vive na heap.

E a heap é dividida em pages. Cada page contém slots. Cada slot guarda um objeto.

Heap
├── Page 0 — [slot][slot][slot][slot]...
├── Page 1 — [slot][slot][slot][slot]...
└── Page 2 — [slot][slot][slot][slot]...

Quando você faz:

user = User.new("Vini")

Ruby:

  1. acha um slot livre
  2. cria o objeto ali
  3. te retorna uma referência

Parece simples. Não é.

Porque a heap não é infinita.

E quando enche, alguém precisa limpar.


O GC entra em cena

O Garbage Collector do Ruby é um mark and sweep com generational + incremental.

Tradução:

  • ele marca o que está vivo
  • libera o que não está
  • prioriza objetos novos (que morrem cedo)
  • faz isso em pedaços (não trava o mundo inteiro de uma vez)

E aí vem o problema clássico:

"Meu Ruby está lento."

Pode ser GC pause.

Pode ser fragmentação.

Pode ser objeto vivendo mais do que deveria (memory bloat).

Sem entender o runtime, você nunca vai descobrir.


Threads, GVL e a ilusão de paralelismo

Ruby tem threads.

Mas o MRI tem o GVL (Global VM Lock).

Só uma thread executa código Ruby por vez.

Isso significa:

  • threads ajudam quando você espera IO
  • threads NÃO ajudam quando você está calculando

É por isso que aplicação web Ruby usa Puma com múltiplos workers (processos) — porque processos têm GVLs separados.

Detalhamos isso num artigo dedicado ao GIL.


Métodos não são funções

No runtime, quando você chama user.save, Ruby precisa:

  1. olhar a classe de user
  2. subir pela ancestors chain
  3. achar o método save
  4. invocar com o self certo

Esse processo se chama method lookup.

E é caro o suficiente para Ruby ter um inline cache dentro da VM, lembrando onde achou o método na última vez.

Se essa cache fura (porque você define método dinamicamente, faz extend em runtime, etc), performance despenca.

Magia tem custo.


Por que isso importa em produção

Você pode passar a vida inteira sem entender o runtime e fazer CRUD funcionar.

Mas no dia que:

  • a aplicação consumir 4GB de memória
  • a thread travar por 800ms num GC pause
  • a CPU ficar a 100% sem entender por quê
  • um método dinâmico derrubar performance em produção

…você vai estar de mãos atadas.

Runtime é o terreno.

Linguagem é só o que você constrói em cima dele.


A grande virada de chave

Quando você entende o runtime, você para de ver Ruby como "uma linguagem dinâmica fofa".

E começa a ver como ele realmente é:

  • uma VM rodando bytecode
  • uma heap sendo gerenciada
  • um GC lutando contra alocação
  • threads brigando pelo GVL
  • caches tentando salvar o seu pescoço

Aí debug deixa de ser superstição.

E performance vira algo que você pode raciocinar.


Conclusão

Linguagem é a parte fácil.

Runtime é onde a engenharia mora.

Quem entende só linguagem escreve código.

Quem entende runtime entrega sistemas que aguentam produção.

E essa diferença não aparece em entrevista.

Aparece às 3 da manhã quando o pager toca.


Entender o runtime do Ruby (não só a linguagem)
Entender o runtime do Ruby (não só a linguagem)