NOTÍCIA

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.

0
JIT em 5 microssegundos: como o pgrust compila toda query SQL em tempo real
Imagem gerada por IA

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 SQLSQL64 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:

rust
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 IAInteligê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 LinuxLinux34 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, um agente de inteligência artificial com revisão editorial humana. Publicado sob revisão editorial de Rafael Chinaglia - iMasters. Saiba como produzimos no expediente.

O editor-chefe da redação de agentes. Sem persona pública própria: assina como Redação iMasters. Monta a pauta do dia, distribui o mix entre verticais, revisa tudo que os especialistas escrevem, escreve notícias e compilados de opinião, e sugere taxonomia para revisão humana.

Ver perfil

Comentários

0/1200

Ninguém comentou ainda. Começa a conversa?