Como o grep funciona tão rápido?

113

Estou realmente surpreso com a funcionalidade do GREP no shell, antes eu costumava usar o método substring em java, mas agora uso o GREP para ele e ele executa em questão de segundos, é incrivelmente mais rápido do que o código java que eu costumava escrever. (de acordo com minha experiência, posso estar errado)

Dito isso, não consegui descobrir como isso está acontecendo. também não há muito disponível na web.

Alguém pode me ajudar com isso?

Cara
fonte
5
É um código aberto para que você possa dar uma olhada por si mesmo. gnu.org/software/grep/devel.html
driis
6
Ridiculous Fish tem um ótimo artigo respondendo exatamente à sua pergunta: ridiculousfish.com/blog/posts/old-age-and-treachery.html
David Wolever
@WilliamPursell Quando o tempo de execução passa em segundos, o JIT provavelmente aqueceu e a diferença entorpecente se deve a (1) grep ser incrivelmente inteligente sobre o que faz e (2) o código Java fazer uma escolha de algoritmo muito ruim para o problema específico em que o grep se concentra.
3
Quanto tempo sua implementação Java gasta iniciando a JVM e quanto tempo realmente gasta executando seu código? Ou pode ser uma questão do algoritmo usado em seu código Java; um algoritmo O (N ^ 2) provavelmente será lento em qualquer idioma.
Keith Thompson

Respostas:

169

Supondo que sua pergunta seja GNU grepespecífica. Aqui está uma nota do autor, Mike Haertel:

GNU grep é rápido porque EVITA VER TODOS OS BYTE DE ENTRADA.

GNU grep é rápido porque ele executa instruções MUITO POUCOS para cada byte que faz olhada.

GNU grep usa o conhecido algoritmo Boyer-Moore, que procura primeiro pela letra final da string de destino, e usa uma tabela de pesquisa para dizer o quão adiante ele pode pular na entrada sempre que encontrar um caractere não correspondente.

GNU grep também desenrola o loop interno de Boyer-Moore e configura as entradas da tabela delta de Boyer-Moore de tal forma que não é necessário fazer o teste de saída do loop a cada passo desenrolado. O resultado disso é que, no limite, GNU grep tem em média menos de 3 instruções x86 executadas para cada byte de entrada que ele realmente olha (e pula muitos bytes inteiramente).

GNU grep usa chamadas de sistema de entrada Unix brutas e evita copiar dados após lê-los. Além disso, GNU grep EVITA QUEBRAR A ENTRADA EM LINHAS. Procurar novas linhas tornaria o grep mais lento por um fator de várias vezes, porque para encontrar as novas linhas ele teria que olhar cada byte!

Então, ao invés de usar entrada orientada a linha, GNU grep lê dados brutos em um grande buffer, pesquisa o buffer usando Boyer-Moore, e somente quando encontra uma correspondência é que ele vai e procura por novas linhas (certas opções de linha de comando como - n desativar esta otimização.)

Esta resposta é um subconjunto das informações obtidas a partir daqui .

Steve
fonte
41

Para acrescentar à excelente resposta de Steve.

Pode não ser amplamente conhecido, mas o grep é quase sempre mais rápido ao usar o grep para uma string de padrão mais longa do que uma curta, porque em um padrão mais longo, Boyer-Moore pode pular para frente em passos mais longos para alcançar velocidades sublineares ainda melhores :

Exemplo:

# after running these twice to ensure apples-to-apples comparison
# (everything is in the buffer cache) 

$ time grep -c 'tg=f_c' 20140910.log
28
0.168u 0.068s 0:00.26

$ time grep -c ' /cc/merchant.json tg=f_c' 20140910.log
28
0.100u 0.056s 0:00.17

A forma mais longa é 35% mais rápida!

Por quê? Boyer-Moore constrói uma tabela de salto para a frente da string de padrão e sempre que há uma incompatibilidade, ele escolhe o salto mais longo possível (do último caractere ao primeiro) antes de comparar um único caractere na entrada com o caractere na tabela de salto.

Aqui está um vídeo explicando Boyer Moore (crédito para kommradHomer)

Outro equívoco comum (para GNU grep) é que fgrepé mais rápido que grep. fin fgrepnão significa 'rápido', significa 'fixo' (consulte a página de manual) e, como ambos são o mesmo programa e usam Boyer-Moore , não há diferença de velocidade entre eles ao pesquisar por fixo strings sem caracteres especiais regexp. O único uso razão I fgrepé quando há um char especial regexp (como ., []ou *) Eu não quero que ele seja interpretado como tal. E mesmo assim a forma mais portátil / padrão de grep -Fé preferível fgrep.

Arielf
fonte
3
É intuitivo que padrões mais longos sejam mais rápidos. Se o padrão fosse de um byte, o grep teria que verificar cada byte. Se o padrão for de 4 bytes, ele pode fazer saltos de 4 bytes. Se o padrão for tão longo quanto o texto, o grep executará apenas uma etapa.
noel
12
Sim, é intuitivo - se você entende como funciona Boyer-Moore.
arielf
2
Mesmo caso contrário, é intuitivo. Seria mais fácil encontrar uma agulha longa em um palheiro do que uma mais curta
RajatJ
2
O contra-exemplo de "ser mais rápido quando mais tempo" são os casos em que você precisa fazer muitos testes antes de falhar e não pode seguir em frente de qualquer maneira. Digamos que o arquivo xs.txtcontenha 100000000 'x's, e você tem grep yx xs.txt, então, na verdade, ele não consegue encontrar uma correspondência mais cedo do que se você encontrasse grep yxxxxxxxxxxxxxxxxxxx xs.txt. A melhoria de Boyer-Moore-Horspool para Boyer-Moore melhora o salto à frente nesse caso, mas provavelmente não serão apenas três instruções de máquina no caso geral.
LRN
2
Obrigado @Tino. Sim, parece que o tempo em que (GNU) grep/fgrep/egrepera apenas hardlinks para o mesmo executável se foi. Eles (e outras extensões, como os z*grep bz*greputilitários que descompactam na hora), são agora pequenos invólucros de shell grep. Alguns comentários históricos interessantes sobre a troca entre um único executável e wrappers de shell podem ser encontrados neste commit: git.savannah.gnu.org/cgit/grep.git/commit/…
arielf