Encontrei um problema de OOM (isso acontece sempre que eu o executo) ao executar
grep -Fxvf file1 file2
tamanho do arquivo1: ~200MB
tamanho do arquivo2: ~300MB
número de registros em cada arquivo: ~300K
comprimento médio de registros: ~1K (somente caracteres ASCII)
a diferença entre dois arquivos é de aproximadamente 18K registros
Memória livre disponível: ~16GB
Tentei com diversas grep
versões diferentes e em VM, WSL e também em servidor físico, mas obtive o mesmo resultado.
Observe que executei o mesmo comando com apenas algumas linhas de ambos os arquivos para identificar que ele não entra em um loop infinito por ter algum caractere especial nos arquivos e foi bem-sucedido.
Isso é normal?
Estou tentando gerar registros file2
que não existem no arquivo file1
.
Já resolvi meu requisito no awk
mesmo ambiente e obtive a saída em menos de 10 segundos, mas estou me perguntando por que os grep
resultados em OOM.
Eu usei o mesmo comando quase sempre quando precisava consultar os mesmos requisitos e até comparei dois arquivos muito grandes, como dois arquivos com ~ 2 GB de tamanho e cada um com ~ 90 milhões de registros e registros contendo no máximo ~ 20 caracteres ASCII sem qualquer problema em as mesmas caixas.
Usei GNU grep
2.7, 2.16 no SLES12 e GNU grep
3.7 no Ubnutu 22.04 no WSL.
Se você olhar o código da implementação GNU de
grep
para saber o que acontece quando invocado comogrep -Fxvf file1 file2
, você verá:file1
memóriaa
inawk
e o mesmo algoritmo usado no originalfgrep
nos anos 70), de modo a minimizar o número de comparações quando se trata de procurar linhas correspondentes em o assunto.3 acontece mesmo quando
-F
combinado com-x
o qual, para que mais do que algumas strings correspondam, provavelmente seria muito mais eficiente usar uma tabela hash (e, ironicamente, uma foi construída apenas no estágio 2).É provável que a última parte 3 consuma mais CPU e exija mais memória.
Correndo:
E variando 10.000, vejo que ele aloca cerca de 96 bytes de RAM para cada byte do arquivo de padrão, então seriam 18GiB para seu arquivo de 200MiB.
Observando isso
gdb
, vemos que ele aloca astruct trie
(64 bytes) para a maioria, se não para todos os bytes no arquivo de padrão, pelo menos.grep
normalmente é usado para procurar uma string ou regexp ou uma pequena lista de strings ou regexps em entradas de tamanho arbitrário, não foi projetado para comparar arquivos.Para isso, use uma tabela hash como faria em
awk
ouperl
com:É o caminho a seguir. Isso só precisa armazenar todo o conteúdo
file1
na memória (mais a sobrecarga da tabela hash). E a correspondênciafile2
é apenas uma pesquisa na tabela hash.Para arquivos já classificados, usar
comm
seria ainda mais eficiente.Agora, olhando para outras
grep
implementações, podemos encontrar ou construir no GNU/Linux:O GNU e o baú de ferramentas herdado (do OpenSolaris provavelmente baseado no original
fgrep
) usam muita memória, mas são "relativamente" rápidos. busybox e ast-open usam menos memória, mas são muito mais lentos, possivelmente usando um algoritmo mais burro .Toybox' é muito rápido (embora ainda significativamente mais lento que a
awk
abordagem) sem usar muita memória (pouco acima do tamanho defile1
). Ele usa uma espécie de tabela hash grosseira em que o "hash" é o primeiro byte da string, portanto, embora faça comparações simples, ele só precisa comparar com a lista de strings para as quais o primeiro byte é o mesmo, que em o caso de strings aleatórias codificadas em base64, como em meus testes, significa que o número de comparações a serem realizadas é dividido por 64.Se em vez de 20.000 linhas longas de 1.000 bytes de texto base64 aleatório, usarmos a saída
seq -f %01000g 20000
onde apenas os últimos bytes das linhas mudam, os tempos se tornam bem diferentes, com GNU/heirloom sendo de longe o mais rápido (menos de um segundo, mostrando o benefício de usar o algoritmo Aho – Corasick), busybox/ast-open/toybox aparentemente demorando uma eternidade (desisti de esperar depois de 5 minutos), toybox' ficando atrás de ast-open (não busybox que se torna o mais lento de longe²) como o o primeiro byte é sempre o mesmo, e também porque todas as comparações precisam comparar os primeiros 990+ bytes, então podemos esperar que seja cerca de 64*990 tão lento quanto para dados aleatórios.¹ Ele também registra em outra tabela onde os padrões foram lidos, embora seja usado apenas ao relatar erros de regexp, portanto, é inútil para strings fixas.
² Talvez porque use
strstr()
para pesquisar umstr
ing dentro de umstr
ing em vez destrcmp()
mesmo com-x
.Conforme mencionado nos comentários e para responder à sua pergunta: Falha porque grep está lendo o conteúdo completo do arquivo1 e usando cada linha desse arquivo como um padrão de texto estático para pesquisar/corresponder no arquivo2
Embora isso funcione em princípio, é muito ineficiente quando você deseja obter linhas iguais em ambos os arquivos.
Conforme sugerido, use:
comm
comm -23 <(sort file2) <(sort file1)
Isso comparará o conteúdo classificado e as linhas de saída exclusivas do arquivo2 (-2 suprime as linhas exclusivas do arquivo1 e -3 suprime as linhas comuns aos dois arquivos).
(ou classifique os dois arquivos antes de passá-los para o comando comm)