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/unparkde 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. ComSO_REUSEPORTescala
~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.
Links
- Release 0.2.0: https://github.com/glide-lang/Glide/releases/tag/v0.2.0
- Repo: https://github.com/glide-lang/Glide
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.
Fonte: https://glide-lang.org