5 pontos por GN⁺ 2023-12-01 | 1 comentários | Compartilhar no WhatsApp
  • ripgrep (rg) é uma ferramenta de busca em linha de comando baseada em Rust que combina a conveniência de busca de código no estilo do The Silver Searcher com o desempenho bruto no nível do GNU grep, com binários para Linux, Mac e Windows
  • Em 25 benchmarks, não houve ferramenta que superasse claramente o ripgrep em desempenho e precisão tanto na busca em um único arquivo grande quanto em diretórios enormes, e o custo do suporte a Unicode também se manteve baixo
  • Inclui tratamento de .gitignore, exclusão padrão de arquivos ocultos e binários, filtro por tipo de arquivo, suporte opcional a PCRE2, busca em vários encodings e arquivos compactados, além de filtros de pré-processamento, ampliando o uso prático de uma ferramenta de busca de código
  • As diferenças entre os testes no repositório do kernel Linux e no OpenSubtitles2016 dependem fortemente de otimização de literais, busca multipadrão com Teddy SIMD, Aho-Corasick, forma de decodificação UTF-8, contagem de linhas e custo do tratamento de .gitignore
  • Ao buscar em paralelo por muitos arquivos pequenos, o memory map pode ficar mais lento, enquanto em um único arquivo grande pode ser vantajoso; por isso, o ripgrep alterna entre busca com buffer intermediário e busca com memory map conforme o caso

A posição que o ripgrep buscou alcançar

  • ripgrep é uma ferramenta de busca em linha de comando que visa reunir a praticidade das ferramentas de busca de código com o desempenho das ferramentas da família grep
  • Os alvos de comparação são GNU grep, git grep, The Silver Searcher(ag), Universal Code Grep(ucg), The Platinum Searcher(pt) e sift
  • Os benchmarks buscavam verificar três pontos principais
    • não há ferramenta claramente superior ao ripgrep tanto em busca em arquivo único quanto em busca em diretórios de grande porte
    • ele oferece suporte adequado a Unicode sem exigir um grande custo de desempenho
    • ao buscar em vários arquivos de uma vez, memory maps muitas vezes podem ser mais lentos, e não mais rápidos
  • O autor é a pessoa que criou o ripgrep e também o mecanismo de regex usado por ele, e reconhece que os benchmarks podem ser seletivos e enviesados

Recursos e comportamento padrão

  • O nome do executável do ripgrep é rg
  • A busca padrão percorre recursivamente o diretório atual, respeita .gitignore e ignora arquivos ocultos e arquivos binários
  • Também há suporte a .rgignore, e os padrões de .rgignore têm prioridade sobre os de .gitignore
  • Com -u, -uu e -uuu, é possível ampliar o escopo para ignorar arquivos de ignore, incluir arquivos ocultos e incluir arquivos binários
    • rg -uuu é semelhante a grep -a -r
  • Há suporte a filtro por tipo de arquivo
    • rg -tpy foo: busca apenas em arquivos Python
    • rg -Tjs foo: exclui arquivos JavaScript
    • é possível adicionar novas regras de tipo de arquivo com --type-add
  • Também oferece vários recursos do grep
    • saída com contexto
    • busca por vários padrões
    • destaque com cores
    • suporte completo a Unicode
  • O mecanismo de regex padrão não oferece suporte a look-around nem backreference, mas esses recursos podem ser usados ao selecionar o mecanismo PCRE2 com -P
  • Também há suporte a alguma detecção automática de UTF-16 e à especificação de encoding com -E/--encoding
    • incluindo UTF-16, latin-1, GBK, EUC-JP e Shift_JIS
  • Com -z/--search-zip, há suporte à busca em arquivos compactados como gzip, xz, lzma, bzip2 e lz4
  • Também há suporte a filtros arbitrários de pré-processamento, como extração de texto de PDF, descompressão adicional, descriptografia e detecção automática de encoding

Motivos para não usar

  • Se a maior prioridade for portabilidade e disponibilidade em qualquer ambiente, o grep padrão, amplamente instalado e aderente a padrões, é a escolha mais adequada
  • Se você depende de algum recurso específico ou bug presente em outra ferramenta, talvez o ripgrep não seja o ideal
  • Em alguns casos extremos de desempenho, outras ferramentas podem funcionar melhor
  • Se não for possível instalar ou se a plataforma não tiver suporte, ele também não poderá ser usado

Estrutura de funcionamento das ferramentas da família grep

  • Ferramentas de busca passam, em geral, por três etapas
    • coletar os arquivos a serem pesquisados
    • realizar a busca de fato
    • exibir o resultado
  • Como ferramentas da família grep precisam buscar bem em arquivos grandes, o desempenho do mecanismo de regex é importante
  • Ferramentas da família ack precisam processar rapidamente a navegação recursiva em diretórios e a aplicação de regras de ignore como .gitignore
  • O ripgrep tenta combinar as duas abordagens
    • mecanismo de regex rápido
    • busca paralela
    • filtragem dos alvos de busca

Coleta de arquivos e tratamento de ignore

  • Em ferramentas da família ack, é importante decidir rapidamente, a partir do diretório atual, quais arquivos serão pesquisados
  • O desempenho da varredura de diretórios é afetado pela quantidade de chamadas stat desnecessárias
  • O ripgrep usa um iterador recursivo de diretórios projetado para fazer o mínimo possível de chamadas de sistema
  • O tratamento de .gitignore tem custo
    • é preciso procurar arquivos de ignore em cada diretório
    • é preciso compilar os padrões de ignore
    • é preciso aplicar os padrões a todos os caminhos candidatos
  • O repositório do kernel Linux tinha 4.640 diretórios e 178 arquivos .gitignore
  • O ripgrep busca oferecer suporte mais completo à semântica de .gitignore, dando prioridade ao padrão correspondente definido mais recentemente
  • O ucg pode ser mais rápido por usar regras de glob baseadas em whitelist em vez de .gitignore, mas pode deixar passar arquivos com extensões que ele não conhece

Diferenças entre mecanismos de regex

  • Em geral, mecanismos de regex se dividem em duas categorias
    • baseados em backtracking: ricos em recursos, mas podem ficar exponencialmente lentos com certas entradas
    • baseados em autômatos finitos: podem ter recursos mais limitados, mas oferecem garantia de tempo linear em relação ao tamanho do texto pesquisado
  • Os mecanismos usados por cada ferramenta são os seguintes
    • GNU grep, git grep: mecanismo próprio baseado em autômatos finitos
    • ripgrep: biblioteca regex de Rust, baseada em autômatos finitos
    • ag, ucg: baseados em PCRE com backtracking
    • pt, sift: biblioteca regex de Go, baseada em autômatos finitos
  • ag e ucg, por usarem PCRE, podem ficar expostos ao pior comportamento de backtracking
  • O padrão de exemplo (a*)* c pode causar problemas em ferramentas baseadas em PCRE, mas as outras ferramentas dos benchmarks o processam sem dificuldade

Otimização de literais e SIMD

  • Em buscas por strings simples, a otimização de busca por literais pode se tornar mais importante do que o mecanismo de regex em si
  • Boyer-Moore é um algoritmo clássico de busca de substring, e pode usar rotinas como memchr para localizar rapidamente posições candidatas
  • Implementações de memchr muitas vezes usam instruções SIMD para examinar 16 bytes de uma vez, alcançando vazões de vários GB/s
  • A biblioteca regex de Rust extrai agressivamente literais de prefixo e sufixo dos padrões
    • foo|bar
    • (a|b)c
    • [ab]foo[yz]
    • (foo)?bar
    • (foo)*bar
    • (foo){3,6}
  • Se a regex inteira puder ser decomposta em um único literal ou em uma alternância de literais, o mecanismo principal de regex pode nem ser usado
  • O ripgrep também extrai literais internos aproveitando a característica de saída por linha
    • exemplo: em \w+foo\d+, ele encontra primeiro foo e depois valida com regex apenas as linhas candidatas
  • Para busca por vários literais, o GNU grep usa um algoritmo semelhante ao Commentz-Walter, enquanto a regex de Rust usa Aho-Corasick ou o algoritmo Teddy SIMD
  • Teddy é um algoritmo de busca multipadrão baseado em SIMD vindo do Intel Hyperscan, e é uma das otimizações centrais que permitem ao ripgrep superar o GNU grep

Método de busca: evitando busca linha por linha

  • Uma implementação ingênua leria o arquivo linha por linha e aplicaria o padrão em cada linha, mas isso é ineficiente porque, na maioria das buscas, correspondências são raras
  • Ferramentas de busca normalmente pesquisam grandes buffers de bytes de uma vez
    • mapeando o arquivo com memory map
    • lendo o arquivo inteiro na memória
    • fazendo busca incremental com um buffer intermediário de tamanho fixo
  • ripgrep, GNU grep e git grep oferecem suporte a busca incremental, o que permite aplicar isso tanto a arquivos quanto a streams
  • A busca incremental é difícil de implementar
    • cálculo de números de linha
    • tratamento quando o buffer termina no meio de uma linha
    • tratamento de linhas longas
    • tratamento de invert match
    • tratamento da exibição de contexto ao redor da correspondência
  • O ripgrep assume essa complexidade de implementação e usa busca incremental, mostrando nos benchmarks resultados mais rápidos do que memory map ao pesquisar muitos arquivos pequenos

Saída e paralelismo

  • Em buscas paralelas, se cada thread escrever a saída imediatamente, os resultados de arquivos diferentes podem se misturar
  • Todas as ferramentas de busca de código com paralelismo escrevem os resultados da busca em um buffer intermediário na memória e serializam apenas a etapa de saída
  • Esse método permite que as threads de busca executem a busca real em paralelo
  • A desvantagem é que o uso de memória pode crescer em casos como um arquivo de 2 GB em que todas as linhas dão match
  • O ripgrep escreve diretamente em stdout, sem buffer intermediário, ao buscar em stdin ou em um único arquivo

Metodologia de benchmark

  • Os benchmarks são divididos com base nos problemas do usuário final
    • busca em grandes repositórios de código
    • busca em um único arquivo grande
  • Os padrões de busca são tendenciosos para literais simples, alternation e expressões regulares leves
  • Como o comportamento padrão varia entre as ferramentas, tentou-se alinhar condições como número de linha, Unicode, .gitignore e whitelist para uma comparação justa
  • As versões avaliadas no benchmark foram as seguintes
    • ripgrep v0.1.2
    • GNU grep v2.25
    • git grep v2.7.4
    • ag commit cda635, PCRE 8.38
    • ucg commit 487bfb, PCRE 10.21 JIT
    • pt commit 509368
    • sift commit 2d175c
  • O ack foi excluído por ser muito mais lento que as outras ferramentas na época
  • O runner de benchmark é o benchsuite, que exige Python 3.5 ou superior, e está incluído no repositório do ripgrep
  • Cada comando executa 3 warm-ups antes da medição, para que o corpus seja carregado no page cache do sistema operacional
  • Cada comando é medido 10 vezes, e são registrados a média e o desvio padrão
  • O ambiente de execução foi Amazon EC2 c3.2xlarge, Ubuntu 16.04, Xeon E5-2680 2.8GHz, 16 GB de memória e SSD de 80 GB
  • Também foram publicados o log de configuração, os resultados resumidos e o CSV bruto

Resultados de busca no código do kernel Linux

  • O benchmark de busca em código foi executado no repositório do kernel Linux compilado, commit d0acc7
  • O motivo de usar o repositório do kernel compilado é que os artefatos de build permanecem no repositório e podem afetar tanto a relevância dos resultados quanto o desempenho
  • Em linux_literal_default, a busca pelo literal simples PM_RESUME revela as diferenças no comportamento padrão de cada ferramenta
    • rg respeita .gitignore e ignora arquivos ocultos e binários
    • ag e pt fazem algo parecido, mas contam o número de linhas
    • ucg não lê .gitignore e busca com base em whitelist
    • sift por padrão busca em quase tudo
    • git grep tem a vantagem de obter do índice do git o conjunto de arquivos a pesquisar
  • Respeitar .gitignore aumenta a relevância dos resultados, mas pode ter custo de desempenho
  • Em linux_literal, rg (whitelist) teve desempenho quase igual ao ucg, e rg (ignore) ficou em nível parecido com git grep
  • rg (ignore) (mmap) e ag (ignore) (mmap) ficaram mais lentos com uso de memory map, e nas mesmas condições rg (ignore) foi muito mais rápido
  • Mesmo em máquina local, as versões com memory map foram mais lentas, embora a diferença tenha sido menor do que na EC2

Unicode e busca sem distinção entre maiúsculas e minúsculas

  • Em linux_literal_casei, pt ficou muito mais lento ao tratar -i como (?i) da regexp do Go
  • sift perdeu menos desempenho porque usa uma abordagem de converter o padrão e o bloco pesquisado para minúsculas, mas essa otimização só cobre maiúsculas e minúsculas ASCII e é incorreta para Unicode
  • O ripgrep transforma a busca case-insensitive em combinações literais quando possível e usa Teddy para localizar rapidamente posições candidatas
  • A busca \wAh em linux_unicode_word verifica se \w com reconhecimento de Unicode encontra resultados como µAh
  • rg e git grep permitiam alternar Unicode; ag, pt, sift e ucg usavam \w apenas ASCII
  • git grep teve um grande custo de desempenho ao ativar suporte a Unicode, mas o ripgrep quase não perdeu desempenho
  • O ripgrep incorpora a decodificação UTF-8 na máquina de estados finitos e faz match diretamente sobre strings de bytes UTF-8, sem etapa separada de decodificação

Diferenças conforme a complexidade da expressão regular

  • Em regex com sufixo literal como [A-Z]+_RESUME, rg e ucg usam _RESUME para localizar candidatos rapidamente
  • Em alternation literal como ERR_SYS|PME_TURN_OFF|LINK_REQ_RST|CFG_BME_EVT, o ripgrep usa Teddy e pode nem acionar o engine principal de regex
  • Mesmo em alternation sem distinção entre maiúsculas e minúsculas, o ripgrep cria prefixes com combinações de maiúsculas e minúsculas, usa Teddy para localizar candidatos e valida apenas os candidatos com a regex completa
  • Na busca por \p{Greek}, apenas Rust regex e Go regex suportavam essa propriedade Unicode, e rg foi muito mais rápido que pt e sift
  • Na busca por \p{Greek} sem distinção entre maiúsculas e minúsculas, sift não conseguiu reportar matches, e pt não tratou corretamente as regras de maiúsculas e minúsculas em Unicode
  • Em padrões sem literais, como \w{5}\s+..., o desempenho do engine de regex aparece diretamente
    • rg continuou relativamente rápido mesmo com suporte a Unicode ativado
    • git grep pagou um custo alto com suporte a Unicode
    • O DFA Unicode lida com um conjunto de estados NFA muito maior do que o DFA ASCII; o exemplo citado é de cerca de 250 estados NFA para ASCII contra cerca de 77.000 para Unicode

Busca em um único arquivo grande

  • O benchmark de arquivo único usa a amostra OpenSubtitles2016
    • a amostra em inglês tem cerca de 1 GB
    • a amostra em russo tem cerca de 1,6 GB
  • Nessa área, o desempenho do engine de regex e as otimizações para literais se tornam mais importantes
  • Em subtitles_literal, o rg foi o mais rápido tanto na busca por Sherlock Holmes quanto por Шерлок Холмс
  • O ripgrep tenta escolher um byte raro em literais para usar com memchr
    • implementações padrão de Boyer-Moore normalmente usam o último byte para buscar candidatos
    • o rg tenta escolher um byte mais raro para pular por mais tempo dentro de um loop otimizado com SIMD
  • Em UTF-8, muitos caracteres do padrão russo começam com \xD0 ou \xD1, então buscar pelo primeiro byte pode ser ineficiente
  • O rg usa uma tabela de frequência pré-computada de 256 bytes e prefere bytes mais raros em vez de \xD0 e \xD1
  • Em um único arquivo grande, como o memory map só precisa ser criado uma vez, a busca com memory map do rg foi cerca de 25% mais rápida que rg (no mmap)

Unicode e alternation em arquivo único

  • Em subtitles_literal_casei, o rg é rápido mesmo tratando corretamente a busca Unicode sem distinção entre maiúsculas e minúsculas
  • O GNU grep paga um custo alto em buscas Unicode sem distinção entre maiúsculas e minúsculas
  • Na busca em russo sem distinção entre maiúsculas e minúsculas, grep (ASCII) parece na prática ignorar -i, e ag reporta 0 matches
  • Em subtitles_alternate, a busca por alternation de vários nomes de personagens foi mais rápida com rg tanto em inglês quanto em russo
  • Na alternation em inglês, rg foi cerca de uma ordem de grandeza mais rápido que o GNU grep
  • Em subtitles_alternate_casei, o rg ficou muito mais lento que antes, mas ainda superou as outras ferramentas em inglês
  • Nesse caso, o número de candidatos literais fica grande demais para o Teddy, então o rg troca Teddy por Aho-Corasick
  • O ripgrep usa um Aho-Corasick “advanced” baseado em tabela de transição, realizando uma transição por byte de entrada

inner literals e padrões sem literais

  • Padrões como \w+\s+Holmes\s+\w+ foram construídos para evitar a otimização de literais de prefixo/sufixo, mas ainda podem aproveitar o literal interno Holmes
  • ripgrep e GNU grep fazem a otimização de inner literal
  • O ripgrep usa o regex-syntax do Rust regex para extrair literais da AST do padrão
  • Na versão em russo \w+\s+Холмс\s+\w+, só ferramentas com suporte adequado a Unicode conseguiam produzir resultados relevantes
  • Em padrões longos como \w{5}\s+..., sem nenhum literal, o rg ficou entre os mais rápidos em inglês, e a versão do GNU grep com suporte a Unicode foi excluída por levar mais de 90 segundos em inglês e mais de 4 minutos em russo
  • O ripgrep mantém o suporte a Unicode e ainda garante desempenho ao incorporar a decodificação UTF-8 no DFA

Benchmarks adicionais

  • everything é um teste irrealista que faz correspondência de todas as linhas com .* no repositório do Linux
    • rg reportou 22.065.361 linhas em 1,081 s
    • ag e pt não reportaram todas as linhas, sugerindo que têm limite de correspondências
  • nothing é um teste que aplica invert match a .* para não reportar nenhuma linha
    • rg marcou 0,302 s e git grep 0,905 s
    • pt e ucg não oferecem suporte a busca invertida
  • context imprime 2 linhas de contexto ao redor de Sherlock Holmes em um corpus de legendas em inglês
    • rg ficou em 0,612 s e sift em 0,717 s, com desempenho parecido
    • ucg não oferece suporte a esse recurso
  • huge busca Sherlock Holmes em todas as 9,3 GB de legendas em inglês
    • rg marcou 1,786 s, GNU grep 5,119 s e sift 3,047 s
    • ucg reportou incorretamente apenas 1.543 linhas na condição de contagem de linhas, e suspeita-se que tenha tido problemas ao buscar em arquivos maiores que 2 GB

Conclusão

  • O ripgrep nem sempre venceu todos os benchmarks na busca no repositório do kernel Linux, mas era difícil dizer que qualquer outra ferramenta tinha vantagem clara em desempenho e precisão
  • O git grep podia ficar alguns milissegundos à frente em certos casos simples, mas quando o padrão ficava mais complexo ou era necessário Unicode, houve casos em que o ripgrep ficou muito à frente
  • Os fatores a seguir contribuíram para o desempenho do ripgrep em busca de código
    • varredura rápida de diretórios com foco em minimizar chamadas a stat
    • correspondência de globs do .gitignore com RegexSet
    • distribuição de trabalho com uma fila work stealing Chase-Lev
    • a escolha de não usar mapeamento de memória ao buscar em muitos arquivos pequenos
    • um mecanismo de expressões regulares rápido
  • Na busca em arquivo único, o ripgrep foi o mais rápido em todos os principais benchmarks ou ficou muito à frente
  • No desempenho em arquivo único, influem memchr baseado em bytes esparsos, Teddy SIMD, Aho-Corasick e DFA com decodificação UTF-8 embutida
  • Nos benchmarks que exigiam recursos Unicode, só rg, GNU grep e git grep mostraram suporte relevante, e GNU grep e git grep em geral pagavam um custo de desempenho alto
  • O mapeamento de memória foi desvantajoso em buscas paralelas em muitos arquivos pequenos no Linux x86_64, vantajoso na busca em um único arquivo grande, e pode ter penalidade extra em ambientes de VM

1 comentários

 
GN⁺ 2023-12-01
Opiniões no Hacker News
  • É definitivamente rápido, e continuo recomendando a combinação com fzf
    Uso como uma função do PowerShell: primeiro procuro com ripgrep, depois aplico uma busca fuzzy sobre os arquivos+textos resultantes e mostro o contexto com bat
    Em projetos com vários repositórios misturados, dá para ir estreitando muito rápido quando “sei que está em algum lugar, mas não sei exatamente a localização ou o nome”
    Esse método veio de https://github.com/junegunn/fzf/blob/master/ADVANCED.md, e mesmo que você não use tudo, vale dar uma olhada para tirar ideias

    • Recomendo ir um passo além e integrar ripgrep-all(rga) com fzf
      Dá para fazer busca fuzzy não só em arquivos de texto, mas também em vários formatos de arquivo, como PDF e zip
      Mais detalhes em https://github.com/phiresky/ripgrep-all/wiki/fzf-Integration
    • Também escrevi isso em uma versão bash
      A ideia é escolher um resultado de rg com fzf, fazer o parsing do arquivo e do número da linha selecionados e abrir com $EDITOR +"${linenumber}" "$file"
    • No Vim, sem fzf+rg, a sensação é de que quase tudo fica quebrado
      É como moer café à mão em vez de usar um moedor elétrico
    • Com fzf, dá para escolher muitos arquivos para adicionar ao Git e pular alguns deles
      Se você colocar fza = "!git ls-files -m -o --exclude-standard | fzf -m --print0 | xargs -0 git add" em [alias] no gitconfig, git fza mostra a lista de arquivos modificados ou ainda não adicionados, e você alterna os itens com espaço enquanto avança para o próximo
      Esse alias e fzf+fd aceleram bastante parte do fluxo de trabalho
      Há também um guia com itens para colocar na configuração do zsh no macOS: https://gist.github.com/aclarknexient/0ffcb98aa262c585c49d4b...
    • Também uso ripgrep quase do mesmo jeito
      Em uma codebase com centenas de repositórios, uso como ponto de partida para ir estreitando arquivos ou projetos, e depois me aprofundo
  • No Emacs, uso ripgrep com os pacotes project.el e dumb-jump
    Talvez não seja a forma mais popular, mas a experiência geral é bem satisfatória
    Basta instalar dumb-jump com package-install e configurar apenas (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)
    Em projetos Python, quando você procura a definição de um identificador com M-. ou C-u M-., o dumb-jump executa um comando rg adequado ao projeto e ao tipo de arquivo atuais e mostra os resultados no buffer Xref
    Ele também suporta ag e, se não houver ag nem rg, volta para grep, mas ao procurar no diretório home inteiro pode ser lento, como esperado

    • Mesmo só com o project.el embutido no Emacs, dá para usar ripgrep com bastante facilidade
      Não é obrigatório ter um pacote externo; para usar no lugar do grep lento em diretórios grandes, basta configurar (setq xref-search-program 'ripgrep)
      Assim, uma busca de projeto como C-x p g foo RET é executada no projeto atual na forma rg -i --null -nH --no-heading --no-messages -g '!*/' -e foo
      Os resultados aparecem no buffer Xref, o que facilita ir para a próxima/anterior ocorrência, pular para o código-fonte e exibir em uma janela dividida com teclas como n, p, RET, C-o
    • Como autor do ripgrep, olhando para essa expressão regular, embora eu não a tenha executado diretamente, acho que dá para remover a flag --pcre2
      Também parece que a segunda e a terceira asserções \b podem ser removidas, embora a primeira talvez seja necessária
    • Deadgrep usa ripgrep e também tem bindings do evil-collection, então dá para usar de forma satisfatória: https://github.com/Wilfred/deadgrep
    • Esse método também é bom, mas quando quero procurar em vários projetos de uma vez ou só em subpastas dentro de um projeto, ainda uso rg.el
      Nessas situações, antigamente eu teria usado rgrep
  • O interessante é que a busca do VS Code agora também funciona com ripgrep por meio de um wrapper em Node.js
    https://www.npmjs.com/package/@vscode/ripgrep

    • Se você está em um ambiente onde pode solicitar ou instalar o VS Code, mas não consegue instalar ripgrep, isso é ótimo
      Dá para encontrar o binário rg dentro do caminho de instalação do VS. Pelo menos no meu ambiente corporativo Windows isso foi possível
    • Eu sempre me perguntava por que a busca do VS Code era tão rápida, sendo ele um app Electron; agora entendi o motivo
    • Não é um recurso novo; ele já está no VS Code há 7 anos
  • Uso ripgrep há cerca de 2 anos e agora ele se tornou uma ferramenta indispensável
    O principal motivo para eu ter migrado do grep foi a facilidade de uso
    Por padrão, ele respeita as regras do .gitignore e ignora arquivos/diretórios ocultos e arquivos binários, então rg search_term directory é muito melhor do que o comando grep correspondente; o ganho de velocidade é um bônus
    Quando uma correspondência é longa demais e bagunça o terminal, costumo usar a opção -M, como em -M 1000

    • -M é realmente excelente
      É especialmente prático para ignorar resultados de arquivos minified que você não quer ver, e também é bom pesquisar só arquivos de uma extensão específica com a opção -g, como em -g *.cs
      O fato de ser um binário portátil e standalone também é útil: quando trabalho em uma máquina nova, coloco o executável e defino o alias de grep para rg; assim, mesmo digitando grep por hábito, quem roda é o rg
  • Isso talvez ainda seja verdade em 2023, mas o problema é que as ferramentas paralelizadas que substituem o grep, como ripgrep ou ag, são tão mais rápidas que o grep tradicional que pequenas diferenças de velocidade entre elas dificilmente servem como critério de distinção
    Uso ag dentro do Emacs em uma base de código de 900 mil linhas e, em um Ryzen Threadripper 2950X de 16 núcleos, ele termina praticamente na hora
    Não sinto necessidade de reduzir algo abaixo de 1 segundo para “um pouco mais abaixo de 1 segundo”
    A característica central das novas ferramentas do tipo grep não é a velocidade; elas precisam ser avaliadas e comparadas de outras formas

    • Em 2016, acho que velocidade era claramente a característica central
      O ag tem penhascos de desempenho bastante grandes, e isso também aparece no post do blog
      Mas a carga de trabalho varia de pessoa para pessoa, então em alguns casos a diferença de desempenho pode não importar
      900 mil linhas não é tanta coisa assim e, para consultas simples, a maioria das ferramentas do tipo grep que não sejam ingênuas processa isso muito rapidamente
      Por outros critérios de comparação, o ag está quase em suporte de vida e parece que quase foi removido do Debian, até alguém o salvar: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=999962
      O post do blog também compara suporte a Unicode, e o ag praticamente não tem suporte a Unicode. Pode não ser importante para todo mundo, mas é um critério não relacionado a desempenho bastante válido
    • Pela minha experiência, todas essas ferramentas ficam fortemente presas a um gargalo de entrada/saída
      O tempo de busca leva tanto quanto o tempo para carregar os arquivos do disco, e as diferenças depois disso dificilmente são significativas
      Se os arquivos estão em cache, o tempo para navegar pelo sistema de arquivos e digitar o comando domina mais que o tempo de busca, então, de novo, fica difícil a diferença de desempenho ser relevante
  • O título precisa de (2016)
    Isto é o anúncio original, não uma informação nova

  • Não é mais rápido que o qgrep
    O modo de funcionamento dos dois é bem diferente, e o qgrep é baseado em re2, mas a velocidade vem por causa do índice
    Em repositórios grandes de arquivos, faz mais sentido usar qgrep com índice do que varrer todos os arquivos toda vez, e fico curioso por que as pessoas esquecem a opção qgrep
    Dito isso, se você precisa de correspondência multilinha em UTF-8, acho que o ripgrep precisa recorrer a outra biblioteca PCRE2 e, por isso, não fica tão rápido

    • Como autor do ripgrep, é verdade que o qgrep tem vantagem sobre ferramentas sem indexação por usar indexação
      Em compensação, é preciso configurar e manter o índice, então a UX não é tão simples quanto “simplesmente executar a busca”
      O motivo de as pessoas não usarem qgrep é parecido com o motivo de não usarem ripgrep dizendo “para mim, grep já é rápido o suficiente”
      Em alvos de busca pequenos, muitas vezes não se percebe a diferença de velocidade entre ripgrep e grep, ou entre qgrep e ripgrep
      Se o ripgrep termina uma busca no kernel Linux em menos de 100 ms, trocar para uma ferramenta indexada em uso interativo padrão pode ou não ser inconveniente o bastante para valer a pena, dependendo do caso, mas em geral não será
      Já pensei na ideia de adicionar indexação ao ripgrep: https://github.com/BurntSushi/ripgrep/issues/1497
      E busca multilinha não exige PCRE2. O motor de expressões regulares padrão também tem suporte a Unicode, e o suporte a busca multilinha permanece mesmo em builds sem PCRE2
  • Depois que migrei do ripgrep para o ugrep, não olhei mais para trás
    A velocidade é parecida, mas ele tem correspondência aproximada, uma TUI útil para revisão de código e também consegue buscar dentro de PDFs e arquivos compactados
    Também é conveniente poder usar opcionalmente a sintaxe de busca do Google
    https://ugrep.com

    • Sou fã entusiasmado do ripgrep, mas recentemente acabei encontrando o ugrep por causa de um recurso que o ripgrep não tem: buscar dentro de arquivos zip compactados
      Dá para buscar sem descompactar no disco
      Trabalho com corpora compactados formados por milhões de pequenos arquivos de texto, e é ótimo não precisar extrair tudo para o sistema de arquivos. Alguns sistemas de arquivos sofrem nessa escala
      Sou grato pelas duas ferramentas e agradeço aos respectivos autores
    • Tenho medo de que, se começarem a usar sintaxe de busca do Google no grep, a maioria dos resultados acabe tentando vender alguma coisa
    • Procurando rapidamente por textos sobre “ugrep vs ripgrep”, vi posts que dão a entender que os autores do ugrep e do ripgrep discutiram no Reddit por alguns anos
      Por exemplo https://www.reddit.com/r/programming/comments/120wqvr/ripgre...
      É só uma conversa sobre ferramentas open source, mas pareceu meio estranho
    • Fico curioso se a TUI é melhor do que encaminhar os resultados para o fzf
      Para mim, parece difícil superar a configurabilidade e a flexibilidade do fzf
    • Obrigado por mostrar isso
      O recurso matador parece ser a compatibilidade com as opções de linha de comando existentes do grep
      É bem bom não precisar aprender um conjunto de opções completamente novo
  • Fico me perguntando por que o grep não é substituído ou melhorado
    Acho que esse assunto já está meio antigo

    • Há muitos motivos que podem explicar isso
      Inércia, compatibilidade, resistência à mudança, coisas como o dilema do inovador. Não estou dizendo isso de forma negativa; tudo isso também se aplica a mim
      Sobre compatibilidade, veja o FAQ: https://github.com/BurntSushi/ripgrep/blob/master/FAQ.md#pos...
    • É parecido com o motivo de eu não trocar a cadeira de 40 anos em que estou sentado agora por uma Razer UltraSeat XR3000-A
      Ela é confortável, combina bem com meu ambiente de trabalho ao redor, e não há motivo para trocar e ajustar tudo de novo
      A analogia só vai até o ponto de eu já ter uma cadeira tipo Razer por perto, servindo para pendurar roupas
    • Alguém que projetou o Unix transformou algumas funcionalidades do sistema em recursos centrais do SO e, ao mesmo tempo, em ferramentas usadas por pessoas; o resultado, décadas depois, é uma consequência estranha do tipo “precisa existir necessariamente um programa chamado xyz, e ele precisa aceitar estes argumentos e se comportar exatamente assim”
    • Já é possível usar várias alternativas como o ripgrep
      Se a ideia for trocar o próprio comando grep por outro utilitário, parece que muita coisa quebraria em comparação ao valor obtido
      Quem quer um grep mais rápido usa outra ferramenta, e quem usa o grep existente continua usando; então isso já está perto do estado ideal
    • O grep é uma ferramenta de uso geral para encontrar texto em todos os tipos de arquivo, e está cravado no padrão UNIX
      Alguns programadores o usam para busca em código-fonte, mas outras pessoas o usam para buscas de texto sem relação com código-fonte ou em scripts, esperando que ele nunca trave
      Já o ripgrep é uma ferramenta especializada e bastante opinativa, projetada principalmente para buscar em repositórios de código-fonte
      Não há muito espaço para tornar a busca de texto de uso geral mais rápida. Usar mmap() traz risco de travamento em arquivos truncados; reduzir a expressividade das expressões regulares poderia deixá-la mais rápida; e também seria possível abandonar o suporte a todos os locales e conjuntos de caracteres e hardcodar apenas UTF-8/UTF-16, mas não se deve fazer isso
  • Procurei no Portage e parece haver também uma versão que lida com outros documentos, como PDFs e arquivos doc
    https://github.com/phiresky/ripgrep-all