JIT em 5 microssegundos: como o pgrust compila toda query SQL em tempo real
Autor de banco de dados escrito em Rust mostra a técnica de copy-and-patch que gera assembly ARM64 direto, sem LLVM, e credita a IA por baixar a barreira de entrada.

Um post publicado no blog malisper.me e discutido no Hacker News detalha como o autor construiu um compilador JIT que gera código em torno de 5 microssegundos, rápido o suficiente para compilar toda query SQL↳SQL64 conteúdosSQL Server – Como evitar SQL Injection?Data · mai 2019Azure SQL DB Managed InstanceData · abr 2019SQL Server – Como evitar SQL Injection? Pare de utilizar Query Dinâmica como EXEC(@Query)Data · abr 2019Ver tudo em Data →, e não apenas um subconjunto delas. A técnica está no coração do pgrust, um banco de dados que ele vem escrevendo em Rust, e o texto reconstrói o método passo a passo usando um motor de expressões regulares como exemplo didático.
Por que compilar em microssegundos importa
JIT (Just In Time) é a prática de gerar código de máquina em tempo de execução, aproveitando informações que só existem no runtime. Segundo o autor, feito corretamente, isso rende ganhos de 2x a 5x, às vezes mais. O caso de uso clássico são interpretadores de linguagem, que só recebem o código a executar em runtime, mas o mesmo vale para parsing de dados cujo schema é desconhecido de antemão, exatamente a situação de um banco processando SQL arbitrário.
O problema histórico é o custo de compilar. O autor afirma que nenhum banco de dados pronto para produção tem hoje um compilador JIT próprio: todos usam LLVM ou geram código C/C++, e as duas rotas sofrem com tempos de compilação altos. É por isso que sistemas como o PostgreSQL só acionam JIT (via LLVM) para queries caras, quando o custo de compilar se paga. Se cada compilação leva microssegundos em vez de milissegundos, essa conta muda: dá para compilar tudo.
Copy-and-patch: stencils em vez de LLVM
A abordagem escolhida é uma variante de copy-and-patch. A ideia é manter templates de assembly para cada operação que se quer compilar, chamados de stencils. Para compilar uma operação, pega-se o stencil correspondente e fazem-se pequenos ajustes conforme os detalhes, como preencher um estêncil de verdade. Encadeando vários stencils preenchidos, monta-se em runtime um programa com desempenho próximo ao de código escrito à mão.
No exemplo do artigo, um motor de regex minimalista (só literais e repetição *) é representado por uma AST em Rust com três nós: Literal, Concatenation e Repetition. O interpretador ingênuo dessa AST tem menos de 20 linhas, mas o benchmark mostra que ele é 10x a 20x mais lento que uma versão escrita à mão especificamente para o regex b(an)*. É essa lacuna que o JIT precisa fechar.
A geração de código ocorre em ARM64 no macOS. Cada stencil é uma função Rust que devolve um array de instruções de 32 bits, com os pontos variáveis preenchidos por manipulação de bits. O stencil de comparação de caractere, por exemplo, insere o byte a comparar e o deslocamento do salto de fallback direto no opcode:
fn stencil_char(byte: u8, stencil_pos: usize, fail_pos: usize) -> [u32; 4] {
[
0x39400009, // ldrb w9, [x0]
0x7100013F | ((byte as u32) << 10), // cmp w9, #byte
0x54000001 | cond_branch_offset(stencil_pos + 2, fail_pos), // b.ne fail
0x91000400, // add x0, x0, #1
]
}Algumas decisões de design deixam o código gerado enxuto: a string termina em byte nulo, então comparações de caractere falham automaticamente no fim da entrada, dispensando checagens de tamanho; e o backtracking usa uma pilha que guarda o endereço de retomada e a posição na string. Os registradores são fixados (x0 para posição e retorno, x1 e x2 para topo e base da pilha, x9 como temporário).
Do buffer à função executável
Para transformar os bytes gerados em algo chamável, o autor usa mmap para alocar memória com permissões de leitura, escrita e execução, marcada com MAP_JIT. No macOS com o modelo W^X da Apple, é preciso alternar a proteção com pthread_jit_write_protect_np antes e depois de copiar o código, e invalidar o cache de instruções com sys_icache_invalidate. Feito isso, o buffer é convertido em ponteiro de função via std::mem::transmute e chamado como qualquer código Rust.
Os números
O benchmark comparou interpretador, JIT e versão escrita à mão em diferentes tamanhos de entrada:
| Tamanho | Interpretador | JIT | Handwritten | Speedup JIT |
|---|---|---|---|---|
| 9 | 45 ns | 3,8 ns | 3,8 ns | 11,7x |
| 33 | 103 ns | 7,9 ns | 10,5 ns | 13,0x |
| 129 | 597 ns | 30 ns | 32 ns | 19,7x |
| 513 | 1.955 ns | 126 ns | 120 ns | 15,5x |
| 2.049 | 8.301 ns | 470 ns | 393 ns | 17,7x |
O resultado central: o código JIT fica empatado com a versão manual, ora um pouco mais rápido, ora um pouco mais lento, e ambos superam o interpretador em mais de uma ordem de grandeza. Como o custo de compilar é de cerca de 5μs, ele se dilui rapidamente em qualquer execução repetida.
A tese sobre IA
O ângulo que rendeu a discussão no Hacker News é a afirmação do autor de que a IA↳Inteligência artificial440 conteúdosUX e IA: Transformando Experiências Digitais com Inteligência ArtificialProduto & UX · jan 2025MCP: O que é e por que você vai ouvir falar disso em breve?AI · jul 2025IA generativa e a urgência de reconstruir nossa relação com a verdadeAI · jun 2025Ver tudo em AI → foi decisiva. Ele conta que nunca havia escrito assembly de verdade, tendo só passado pelo CTF microcorruption, e que teria dificuldade real para acertar as instruções e os ajustes de bits sem ajuda. Com um agente de código, diz, bastou passar a forma geral do compilador para que os detalhes de baixo nível fossem resolvidos.
A partir daí ele desafia o meme de que "IA não ajuda porque escrever código nunca foi a parte difícil". Para compiladores JIT, argumenta, escrever o código era a parte difícil, e a raridade desse tipo de software na prática sugere que implementá-lo historicamente era custoso demais para valer a pena. A conclusão é a tese do próprio projeto: se bancos de dados sempre estiveram entre os softwares mais difíceis de construir, LLMs abrem espaço para projetos mais ambiciosos nesse território.
O que fica em aberto
O exemplo do post é um brinquedo: regex sem alternância, sem lookbehind e com a AST já parseada. Ele demonstra a mecânica, mas não a complexidade de compilar SQL real, com joins, agregações e tipos variados. O código também é específico para ARM64 no macOS, deixando de fora x86-64 e Linux↳Linux34 conteúdosKali Linux em um Servidor VPS: como, quando e por que usar?DevSecOps · dez 2024Construindo um Windows Service ou Linux Daemon com Worker Service & .NET Core – Parte 2Dev (Back & Front) · jul 2020Criando uma WebApi utilizando .NET, Linux e VSCodeDev (Back & Front) · ago 2019Ver tudo em DevSecOps →, o que importa para quem roda banco em servidor. E, sendo uma abordagem de copy-and-patch escrita à mão com auxílio de IA, a manutenção e a correção dos stencils gerados por assembly continuam sendo responsabilidade de quem revisa, um ponto que o próprio autor deixa implícito ao descrever a IA como ferramenta para "lidar com os detalhes".
Para quem constrói runtimes, engines de dados ou interpretadores no Brasil, o take prático é que a barreira de escrever um JIT próprio, em vez de depender de LLVM, ficou mais baixa, e que compilar em microssegundos habilita aplicar otimização em tempo real de forma abrangente. O código completo e o projeto estão no GitHub do pgrust.
Fonte: Hacker News
Este artigo foi escrito por Redação iMasters. Conteúdo produzido por agente de IA da redação iMasters, sob revisão editorial humana. Saiba como produzimos no expediente.
Uber Eats reconstrói pipeline de busca e corta latência em 50%
A empresa trocou a métrica que media velocidade, reduziu trabalho de retrieval e ranking e usou IA agentic para achar otimizações. O resultado: metade do tempo de ponta a ponta na busca do Uber Eats.













