11 pontos por GN⁺ 2026-03-21 | 2 comentários | Compartilhar no WhatsApp
  • O parser WASM escrito em Rust é estruturalmente rápido, mas a cópia de dados e a sobrecarga de serialização na fronteira JS-WASM se revelaram um gargalo de desempenho
  • O retorno direto de objetos via serde-wasm-bindgen foi de 9% a 29% mais lento que a serialização em JSON, devido ao custo de conversões detalhadas entre runtimes
  • Ao portar todo o pipeline para TypeScript, foi possível alcançar desempenho por chamada de 2,2 a 4,6 vezes mais rápido na mesma arquitetura
  • No processamento em streaming, uma melhoria de O(N²) → O(N) com cache por sentença garantiu velocidade total de processamento 2,6 a 3,3 vezes maior
  • Como resultado, confirmou-se que o WASM é adequado para chamadas pouco frequentes e intensivas em computação, mas inadequado para parsing de objetos JS ou funções chamadas com muita frequência

Estrutura e limites do parser WASM em Rust

  • O parser openui-lang é composto por um pipeline de 6 etapas que converte a DSL gerada por LLM em uma árvore de componentes React
    • Etapas: autocloser → lexer → splitter → parser → resolver → mapper → ParseResult
    • Cada etapa executa tokenização, análise sintática, resolução de variáveis, transformação de AST etc.
  • O código Rust em si é rápido, mas os processos de cópia de strings e serialização/desserialização de JSON entre JS↔WASM ocorrem a cada chamada
    • Cópia da string de entrada (JS→WASM), parsing interno em Rust, serialização do resultado em JSON, cópia do JSON (WASM→JS), desserialização em JS
  • Essa sobrecarga na fronteira passou a dominar o desempenho total, e a velocidade de computação do Rust não era o gargalo

Tentativa com serde-wasm-bindgen e fracasso

  • Para evitar a serialização em JSON, foi aplicado serde-wasm-bindgen, que retorna structs Rust diretamente como objetos JS
  • Porém, observou-se uma lentidão de 30%
    • O JS não consegue ler diretamente a memória de structs Rust, e como o layout de memória difere entre os runtimes, é necessária uma conversão campo a campo
    • Já a serialização em JSON gera uma string uma única vez dentro do Rust e depois é processada em JS com JSON.parse, que é otimizado
  • Resultado do benchmark
    Fixture Round-trip JSON serde-wasm-bindgen Variação
    simple-table 20.5µs 22.5µs -9%
    contact-form 61.4µs 79.4µs -29%
    dashboard 57.9µs 74.0µs -28%

Migração para TypeScript e ganho de desempenho

  • A mesma estrutura de 6 etapas foi totalmente portada para TypeScript, removendo a fronteira WASM e executando diretamente dentro do heap do V8
  • Resultado do benchmark por chamada
    Fixture TypeScript WASM Ganho de velocidade
    simple-table 9.3µs 20.5µs 2.2x
    contact-form 13.4µs 61.4µs 4.6x
    dashboard 19.4µs 57.9µs 3.0x
  • Só remover o WASM já reduziu drasticamente o custo por chamada, mas a ineficiência da estrutura de streaming ainda permanecia

O problema O(N²) no parsing em streaming e a melhoria

  • Quando a saída do LLM é entregue em vários chunks, surge a ineficiência O(N²) de reparsear toda a string acumulada a cada vez
    • Ex.: parsear 50 vezes um documento de 1000 caracteres em blocos de 20 → total de 25.000 caracteres processados
  • Como solução, foi introduzido cache incremental por sentença
    • Sentenças concluídas são armazenadas em cache, e apenas a sentença em andamento é parseada novamente
    • A AST em cache e a nova AST são mescladas para retornar o resultado
  • Benchmark considerando o stream completo
    Fixture TS ingênuo TS incremental Ganho de velocidade
    simple-table 69µs 77µs Nenhum
    contact-form 316µs 122µs 2.6x
    dashboard 840µs 255µs 3.3x
  • Quanto mais sentenças houver, maior o efeito do cache, e a vazão total melhora linearmente

Lições sobre o uso de WASM

  • Casos adequados
    • Tarefas intensivas em computação e com pouca interação: processamento de imagem e vídeo, criptografia, simulação física, codecs de áudio etc.
    • Port de bibliotecas nativas existentes: SQLite, OpenCV, libpng etc.
  • Casos inadequados
    • Parsing de texto estruturado em objetos JS: o custo de serialização se torna dominante
    • Funções chamadas com frequência sobre entradas curtas: o custo da fronteira é maior que o da computação
  • Lições principais
    1. É preciso fazer profiling do gargalo antes de escolher a linguagem
    2. O envio direto de objetos com serde-wasm-bindgen é mais caro
    3. Melhorias na complexidade algorítmica são mais eficazes do que trocar de linguagem
    4. WASM e JS não compartilham o heap, e o custo de conversão sempre existe

Resultado final: com a migração para TypeScript e o cache incremental, foi alcanhada uma melhoria de 2,2 a 4,6 vezes por chamada e de 2,6 a 3,3 vezes no stream completo

2 comentários

 
bbulbum 2026-03-23

Será que a intenção não era meio que dar uma cutucada irônica num texto super avançado sobre otimização de performance em Rust..

 
GN⁺ 2026-03-21
Comentários do Hacker News
  • O verdadeiro ponto central não é TypeScript em vez de Rust, e sim a correção do algoritmo de streaming, reduzindo de O(N²) para O(N)
    Só essa mudança, com cache no nível de instruções (statement), já trouxe um ganho de 3,3x
    Independentemente da escolha da linguagem, do ponto de vista do usuário, o principal fator para a melhora de latência foi isso
    Dá a sensação de que o título subestimou esse ponto de engenharia tão interessante

    • O projeto uv também é assim. As pessoas só gritam “rust rulez!”, mas o ganho real vem de melhorias algorítmicas, não da linguagem
    • Obrigado por atravessar o clickbait e apontar o essencial
      O texto em si é interessante, mas ultimamente estou cansado de títulos feitos para gerar clique
    • A expressão n² parece um pouco exagerada
      O método mede o tempo de cada chamada e usa a mediana (median), mas, em um ambiente de navegador com lógica de defesa contra timing attacks nos motores JS, fico em dúvida sobre a precisão
    • No fim, acho que está mais para um título enganoso
  • Dizer “reescrevemos o código da linguagem L para M e ficou mais rápido” é um resultado esperado
    Porque isso cria uma oportunidade para desfazer decisões emaranhadas e equivocadas e aplicar abordagens melhores que surgiram depois
    Na verdade, mesmo se L=M, vale a mesma coisa: o ganho de desempenho vem do processo de reescrita e redesign, não da linguagem

    • Agora, se uma terceira pessoa reescrever a versão em TypeScript de volta para Rust sem conhecer o original, talvez o desempenho melhore de novo
    • Vejo com frequência ganhos de melhoria mesmo quando algo é reescrito na mesma linguagem
  • Fui investigar mais a fundo para melhorar o desempenho da serialização de objetos na fronteira entre Rust e JS
    A abordagem do serde não me pareceu boa em termos de desempenho, e resumi uma tentativa de melhorar isso no meu post de blog

  • Eu estava me perguntando por que o Open UI não fazia trabalho relacionado a WASM
    Mas aí essa nova empresa passou a usar o nome Open UI, o que me confundiu
    O Open UI W3C Community Group original é, há mais de 5 anos, o grupo que cria padrões como popover em HTML, select customizável, invoker command e accordion
    Eles estão fazendo um trabalho realmente excelente

  • Eles disseram que integraram serde-wasm-bindgen na tentativa de “pular a ida e volta em JSON”, mas no fim isso parece uma reinvenção do JSON em formato binário
    Hoje em dia o JSON do V8 já é muito otimizado, e implementações como simdjson conseguem processar na casa de gigabytes por segundo
    Acho improvável que JSON seja o gargalo

  • Gostei muito do design daquele blog
    A barra lateral do tipo “scrollspy”, que destaca os headings conforme a posição da rolagem, ficou especialmente boa
    Pelo que o Claude me disse, parece ter sido feito com fumadocs.dev

    • Interessante. Acho que eu também deveria criar em breve um bom site de documentação
  • Não ficou claro para mim qual é exatamente o propósito do parser Rust WASM
    O texto não explica bem essa parte e acho que precisaria de mais detalhes

    • Pelo que dizem, eles usam uma linguagem dedicada para definir componentes de UI gerados por LLM
      Isso parece servir para evitar vazamento de informações causado por prompt injection
      O parser compila os chunks transmitidos em streaming pelo LLM para montar a UI em tempo real
      Antes, o parser reiniciava do zero a cada chunk; ao mudar isso para um processamento incremental (durante a migração de Rust para TypeScript), o desempenho melhorou bastante
  • Fiquei na dúvida se hoje em dia o TypeScript não estaria rodando com base em Golang

    • Acho que isso se refere ao projeto em andamento para reescrever o compilador TypeScript em Go
  • Brincando, mas talvez se reescreverem de novo em Rust tenham outro ganho de desempenho de 3x /s