A ferramenta `uniq` mais rápida do linux

8

Eu tenho um arquivo de texto grande (1,5 G),

Quero saber qual é a ferramenta mais rápida e confiável no Linux.

Eu costumo usar:

awk '!x[$0]++' file.txt

Mas quando eu uso o htopcomando, vejo que meu uso de memória está aumentando.

Quero saber qual é a mais rápida e confiável para arquivos enormes.

uniq?
sort?
sed?
awk?

Por quê?

MLSC
fonte
Você já tentou executá-los, possivelmente com time?
choroba
tempo é importante e também o uso de memória e confiabilidade (quero dizer que um faz o seu trabalho com precisão)
MLSC
Ainda não ... Mas eu fiz alguns testes antes ... e perguntar em algum lugar, alguns caras me disse awk é o uso de memória best..but no htop ... Eu vejo está aumentando
MLSC
3
@MortezaLSC: É uma troca. Quanto mais rápido o programa, mais memória é usada.
cuonglm

Respostas:

16

Vamos considerar como cada solução funciona.

  • uniqIsso requer que o arquivo já esteja classificado. Caso contrário, você deve canalizá-lo sortprimeiro, o que significa que sorté necessário ler o arquivo inteiro na memória, reordená-lo ( O(n log n)) e depois gravá-lo no canal. O trabalho de uniqé muito barato, uma vez que ele só precisa comparar linhas adjacentes de sua entrada.

  • sort -uIsso combina o trabalho de sort | uniq. Isso precisa coletar todas as entradas exclusivas na memória, como o awkscript, mas também perde tempo classificando-as antes de produzir a saída. Isto é O(n log n), embora neste caso nseja o número de itens exclusivos, nem todas as entradas. Então é melhor que o cachimbo.

  • sedNão sei por que você listou isso, pois não consigo pensar em uma boa maneira de fazer isso sed. Talvez se você primeiro classificá-lo e canalizar para um sedscript, existe uma maneira de comparar linhas adjacentes. Então, sedseria apenas fazer o que uniqfaz, e uniqprovavelmente faz o mais eficientemente possível.

  • awkProvavelmente, é o melhor, porque apenas faz a quantidade mínima de trabalho necessária. Ao ler cada linha, ele faz uma pesquisa de hash eficiente para verificar se a linha já está em sua memória e armazena apenas as linhas exclusivas como chaves de hash e um contador como valor. (Se a linha não estava presente anteriormente, a condição será verdadeira, portanto a linha será impressa. Caso contrário, não será.) Isso usa O(n)tempo e O(uniq n)memória.

Todo método utilizará uma quantidade considerável de memória, para classificar a entrada ou acompanhar quais entradas foram vistas, para que possam remover duplicatas.

Barmar
fonte
1
+1 A explicação sobre awktambém explica por que usa quantidades crescentes de memória. Qualquer coisa que faça uma classificação acabará fazendo isso também, apenas 1) provavelmente usará tudo de uma vez, 2) poderá usar um pouco mais, dependendo do número de chaves únicas ou duplicadas.
goldilocks
@ Perdão Barmar, Mas quando eu tenho um arquivo grande (16 G) com capacidade de memória 8G, então o que vai acontecer com a minha memória?
MLSC
8
@goldilocks, sortrecorre a arquivos temporários (de maneira inteligente) para evitar encher a memória. Seu uso de memória é vinculado. O limite é personalizável com algumas implementações de classificação. É mais eficiente permitir que o sistema troque a memória aleatoriamente para o disco (o que também afeta também os aplicativos no sistema).
Stéphane Chazelas
Isso é verdade. Portanto, se você se deparar com um caso em que awka memória está acabando, sorttalvez seja a única solução, pois foi projetada para lidar com isso. Por outro lado, toda a leitura e gravação em disco reduzirá a velocidade, portanto, provavelmente levará muito tempo para ser concluída. Se você estiver lidando com quantidades tão grandes de dados, provavelmente deverá usar um DBMS em vez de arquivos de texto.
Barmar
@ Barmar Como você deduziu que o tempo de reordenar aumenta como O(n log n)? Ou apenas você conhece isso de outro lugar?
jimmij
0

Eu só queria ressaltar que o gnu uniqparece terrivelmente lento, mesmo em uma lista classificada.

Eu apenas tentei obter uma lista de prefixos de diretório de uma lista de nomes de arquivos classificados:

$ pv all_files | cut -d '/' -f 1,2,3,4 | uniq > all_prefixes

36.7GiB 0:07:41 [81.4MiB/s]

$ pv all_files | cut -d '/' -f 1,2,3,4 | sort -u > all_prefixes2

36.7GiB 0:03:14 [ 193MiB/s]

$ pv all_files  | cut -d '/' -f 1,2,3,4 | awk '!x[$0]++' > all_prefixes3                                        
36.7GiB 0:02:18 [ 270MiB/s] 

sort -u parece duas vezes mais rápido que o uniq, e isso ocorre com a leitura de stdin e escrita para stdout, então não vejo ainda nenhuma paralelização. Eu não tenho idéia por que o uniq deve ser muito mais lento que a classificação, já que não precisa classificar a lista ...

A saída desse comando é muito pequena (existem muitas duplicatas), apenas 264kb e a classificação termina instantaneamente após a conclusão do PV.

As mesmas velocidades permanecem se você inverter a ordem dos comandos, meu fluxo é limitado pelo tempo da CPU aqui, não pelo acesso ao disco e caches (eu tenho apenas 8 GB de RAM e minha troca não é usada)

Estou executando isso em uma máquina fedora 31 com gnu coreutils sort e uniq e gnu awk; locale está definido como en_US.UTF-8

ATUALIZAÇÃO , como isso me intrigou bastante, fiz mais alguns testes, vamos cortar a parte do caminho e garantir que o arquivo esteja bem classificado

cat all_files | cut -d '/' -f 1,2,3,4 | sort -T . > test

Isso leva 8,4 minutos. teste agora é 7,9 GB grande

vamos executar essas ferramentas no arquivo em vez de em um pipe, isso permitirá que essas ferramentas otimizem um pouco mais, como a classificação será multithread. e também de um ssd mais rápido.

Você pode não perceber que essa classificação também está consumindo muita memória, pois faz truques inteligentes com arquivos temporários em / tmp, que podem ser tmpfs e estarão em sua memória RAM (tente classificar um arquivo maior que / tmp, você irá correr para o espaço problemas, é por isso que eu preciso da bandeira -T. no comando acima)

$ time sort -u test > /dev/null
339.24user 3.54system 1:28.87elapsed 385%CPU (0avgtext+0avgdata 2365856maxresident)k
9555544inputs+0outputs (0major+591298minor)pagefaults 0swaps

$ time awk '!x[$0]++' test > /dev/null                                                                                                                             
51.15user 1.55system 0:52.94elapsed 99%CPU (0avgtext+0avgdata 10976maxresident)k
0inputs+0outputs (0major+1923minor)pagefaults 0swaps

$ time uniq test > /dev/null                                                                                                                                  
421.89user 2.76system 7:06.63elapsed 99%CPU (0avgtext+0avgdata 1980maxresident)k
52712inputs+0outputs (0major+79minor)pagefaults 0swaps

Parece que sua solução awk é a mais rápida dessas 3 e, na verdade, usa menos memória

update2 e agora com uma localidade mais simples

$ export LC_ALL=c
$ time sort -u test > /dev/null                                                                                                                                             1.2m ? Tue Apr 21 17:09:22 2020
119.18user 3.64system 0:38.24elapsed 321%CPU (0avgtext+0avgdata 2013472maxresident)k

$ time awk '!x[$0]++' test > /dev/null                                                                                                                                1161ms ? Tue Apr 21 17:07:31 2020
67.23user 2.50system 1:10.16elapsed 99%CPU (0avgtext+0avgdata 10480maxresident)k
7187520inputs+0outputs (0major+1912minor)pagefaults 0swaps

$ time uniq test > /dev/null                                                                                                                                               
22.05user 2.02system 0:24.24elapsed 99%CPU (0avgtext+0avgdata 1488maxresident)k
2959648inputs+0outputs (1major+72minor)pagefaults 0swaps

Desta vez, o uniq vence a corrida ... como Stéphane Chazelas sugere nos comentários, definir seu local para C torna a classificação e o uniq muito mais rápidos!

Jens Timmerman
fonte
Qual implementação sorte uniq? Qual local?
Stéphane Chazelas 20/04