1 pontos por GN⁺ 2025-03-19 | 1 comentários | Compartilhar no WhatsApp
  • Compilar manualmente o pacote de código-fonte do jq fornecido pelo Ubuntu pode melhorar o desempenho em até 90%
  • O desempenho é maximizado ao aprimorar o compilador, as flags de otimização e o alocador de memória

Configuração

  • jq é usado para processar arquivos GeoJSON em formato JSON
    • Execução de uma consulta que imprime o nome da cidade de todos os parcels acima de um determinado valor no mapa de parcels do Alameda County Assessor, com 500 MB
  • Em um sistema Ryzen 9 9950X, leva cerca de 5 segundos com o arquivo em cache, então a ideia foi tentar melhorar isso

Etapa 1: reconstruir o pacote

  • Baixar o código-fonte do jq no Launchpad e recompilar sem nenhuma flag
  • Resultado: ganho de desempenho de 2% a 4%
  • Resultados do benchmark
    • jq compilado: média de 4,517 segundos
    • pacote padrão do Ubuntu: média de 4,641 segundos
    • melhoria de desempenho: 1,03x mais rápido

Etapa 2: usar Clang e flags avançadas de otimização

  • Compilar com Clang-18 e usar nível de otimização e LTO
  • Principais flags usadas:
    • -O3 → nível de otimização mais alto
    • -flto → aplicação de Link-Time Optimization
    • -DNDEBUG → exclusão de código de depuração
  • Resultados do benchmark
    • jq compilado: média de 3,853 segundos
    • pacote padrão do Ubuntu: média de 4,631 segundos
    • melhoria de desempenho: 1,20x mais rápido

Etapa 3: adicionar TCMalloc

  • Uso de TCMalloc no lugar do malloc padrão da GNU libc
  • Compilação após adicionar -L/usr/lib/x86_64-linux-gnu -ltcmalloc_minimal
  • Resultados do benchmark
    • jq compilado: média de 3,253 segundos
    • pacote padrão do Ubuntu: média de 4,611 segundos
    • melhoria de desempenho: 1,42x mais rápido

Etapa 4: aplicar preload dinâmico do TCMalloc

  • Uso de TCMalloc por preload dinâmico no pacote padrão do Ubuntu
  • Resultados do benchmark
    • jq padrão: média de 4,601 segundos
    • jq com TCMalloc: média de 4,082 segundos
    • melhoria de desempenho: 1,13x mais rápido

Etapa 5: testar preload dinâmico com outros alocadores

  • Teste com jemalloc e mimalloc, outros alocadores de memória fornecidos no Ubuntu
  • O mimalloc apresentou o melhor desempenho
  • Resultados do benchmark
    • jq padrão: média de 4,123 segundos
    • jq com TCMalloc: média de 4,130 segundos
    • jq com Jemalloc: média de 3,510 segundos
    • jq com Mimalloc: média de 3,154 segundos → ganho de desempenho de 1,31x

Etapa 6: compilar diretamente com mimalloc

  • Vincular mimalloc estaticamente em vez de usar preload dinâmico
  • Desempenho maximizado
  • Resultados do benchmark
    • jq compilado: média de 2,428 segundos
    • pacote padrão do Ubuntu: média de 4,606 segundos
    • melhoria de desempenho: 1,90x mais rápido

🚀 Resultado final

  • O jq compilado manualmente ficou 90% mais rápido que o pacote do Ubuntu
  • Desempenho no processamento de 13.000 arquivos JSON de 2,2 GB:
    • jq compilado: 0,755 segundos
    • jq padrão: 1,424 segundos
    • melhoria de desempenho: cerca de 2x

1 comentários

 
GN⁺ 2025-03-19
Comentários do Hacker News
  • O título "Reconstruir pacotes do Ubuntu e trocar o alocador de memória para ficar 90% mais rápido" parece clickbait

    • Trata-se de apenas um pacote, e parte do ganho de desempenho não foi obtida por recompilação
    • Já tive experiência em pré-carregar o jemalloc para substituir a implementação de malloc, com resultados positivos na estabilização do uso de memória
    • Isso resolveu um problema de vazamento de memória, e é bem provável que na verdade fosse um problema de fragmentação de memória, não da aplicação em si
  • Engenharia é a arte do compromisso

    • O artigo explica que a maior parte do ganho de desempenho veio da especialização do alocador de memória
    • Em projetos multithread, a escolha do alocador é importante, e um ganho de velocidade em um projeto pode causar travamentos em outro
    • A estratégia de realocação também deve ser considerada, e é preciso escolher entre estabilidade de longo prazo e velocidade de curto prazo
    • Durante o desenvolvimento de um editor de vídeo, experimentei vários alocadores e descobri que o alocador do glibc oferece estabilidade de longo prazo
  • O Gentoo Linux é um sistema operacional projetado para ser otimizado para o uso específico de cada usuário

    • Depois da configuração inicial, é simples de usar, e lembro de ter feito muitos amigos no canal do Gentoo Linux
    • As primeiras versões do ChromeOS eram basicamente uma instalação customizada do Gentoo Linux
  • Instalar manualmente pacotes como o jq pode deixá-los fora das atualizações de segurança

    • Por exemplo, houve atualizações de segurança do onigurama, e se uma situação assim se repetir, isso pode gerar vulnerabilidades
    • Há casos em que várias vulnerabilidades de segurança, como a CVE-2017-9224, foram corrigidas
  • Usar um malloc não oficial pode causar bugs estranhos

    • Se você for além das flags usadas pelos desenvolvedores, a chance de problemas é alta
  • Ao ler que uma mudança simples pode trazer um grande ganho de velocidade, dá vontade de avisar o desenvolvedor do jq

    • O artigo parece não ter considerado essa opção, e ela também não é mencionada nos comentários
  • Compilar pacotes a partir do código-fonte ou baixar binários oficiais pode ser útil

    • Era difícil verificar atualizações de instalações manuais e pacotes compilados do código-fonte, então desenvolvi uma ferramenta para resolver isso
  • O recurso cargo install do Rust é útil por permitir otimizações para plataformas específicas

    • jaq e yq são opções que costumo usar com frequência para melhorar o desempenho ao usar o jq
  • Depois de trocar o alocador de memória, é possível reconstruir pacotes do Ubuntu para ficarem 90% mais rápidos

    • Isso provavelmente também funcionaria no Debian e no RedHat
    • No começo, achei que o artigo fosse sobre transformar o Ubuntu em Linux From Scratch