1

Update da Glide: saiu a 0.2.0, e o @leaf deixou o runtime em ~192 bytes por task concorrente (mais leve que tokio e goroutine nos meus benchmarks)

Fala, pessoal.

Há um tempo postei aqui sobre a Glide, uma linguagem compilada que eu
comecei pra experimentar uma pergunta: dá pra ter a praticidade de Go
com mais controle sobre memória, erros e runtime? (resumo de novo no
final pra quem não viu).

Saiu a 0.2.0 e eu queria voltar pra falar de duas coisas: o que mudou
desde o primeiro post, e principalmente a parte de runtime/performance,
que é onde eu mais mexi.

O que mudou desde a 0.1.1

Foi um ciclo grande. Os destaques:

  • Tuplas anônimas com retorno multi-valor e destructuring:
    let (q, r) = divmod(17, 5);
  • Tipos inteiros de largura fixa (i8..i256, u8..u256, f32/f64).
    Os nomes antigos (int, float) foram aposentados.
  • HTTP/2 e HTTP/3 (cliente e servidor, com 0-RTT no H3).
  • Segurança de memória mais forte: borrows non-lexical, e escape de
    arena / use-after-free agora viram erro de compilação.
  • Reactors por SO: io_uring no Linux, kqueue no macOS/BSD, IOCP no
    Windows.
  • Proc-macros novos, gerenciador de pacotes com deps git/path,
    cross-compile plug-and-play via Zig e suporte a aarch64.

A parte que eu mais cuidei: o runtime

A concorrência da Glide é M:N (corrotinas em cima de poucas threads),
estilo Go: spawn + canais tipados, sem async/await. O problema
clássico desse modelo é o custo de cada task: normalmente cada uma ganha
uma stack própria, e mesmo crescendo sob demanda existe um piso de uma
página (~4 KB no Linux). Um milhão de tasks já vira gigabytes de RSS só
de stack.

Nesse ciclo eu trabalhei pra baixar esse custo em duas frentes.

1. Stacks que crescem sob demanda. A corrotina normal começa pequena
e cresce via fault handler. Isso já coloca o custo por task bem abaixo de
uma goroutine.

2. @leaf: tasks sem stack. Essa é a novidade que eu queria mostrar.
Se uma função é "folha" (não faz chan, não dorme, não faz I/O, ou seja,
nunca precisa parar e ceder a CPU), eu marco ela com @leaf e o runtime
roda ela inline no próprio worker, sem nunca alocar uma stack de
corrotina. O custo cai pra basicamente o tamanho do struct da task.

@leaf
fn hello(method: string, _path: string) -> string {
    return "hello\n";
}

fn main() -> i32 {
    http_listen_sm_workers(8080, 4, hello);
    return 0;
}

O contrato é checado: se uma função @leaf tentar parar (chan/sleep/IO),
o runtime aborta com erro claro em vez de corromper algo.

Os números (nos meus benchmarks, Linux)

Medindo RSS por task concorrente:

  • Glide com @leaf: ~192 bytes/task
  • tokio: ~403 bytes/task
  • goroutine (Go): ~9 KB/task (piso de stack)

Ou seja, no caso folha o @leaf ficou o mais leve dos três que eu
consegui medir. Um milhão de tasks @leaf cabe em poucas centenas de MB.

Outras medições do mesmo ciclo:

  • park/unpark de canal: caiu de ~23 µs para ~270 ns por ciclo
    (empatado com Go, ~267 ns nas minhas medições).
  • Servidor HTTP no fast path (state machine + @leaf): ~180k req/s num
    hello-world, ~95% do Axum no mesmo hardware. Com SO_REUSEPORT escala
    ~6x de 1 pra N workers.

Sendo honesto

Isso é benchmark meu, em hello-world / microbench, em hardware
específico, e a maior parte do ganho de footprint é no caso @leaf
(folha, opt-in). Não é "a Glide é mais rápida que tudo". É: o modelo de
runtime ficou bem leve quando você consegue marcar o caminho quente como
folha, e mesmo o caminho geral (stack que cresce) ficou mais barato que
goroutine.

A Glide continua pré-1.0 e o compilador é self-hosted (tem um seed em C
só pro bootstrap, depois ela compila a si mesma). A 0.2.0 já sai com
binários nativos pra Linux (x86_64/aarch64), macOS (Apple Silicon) e
Windows.

Quem mexe com runtime/scheduler: o que vocês acham da abordagem do
@leaf (opt-in, contrato de "não pode parar" checado em runtime)? Pra
mim foi o melhor custo-benefício entre footprint e ergonomia, mas tô
curioso pra ouvir furos nessa ideia.

Carregando publicação patrocinada...