Precisa de algo que seja mais rápido que "wc -l"

12

Para um arquivo realmente grande como 1 GB, wc -lé lento. Temos uma maneira mais rápida de calcular o número de novas linhas para um arquivo específico?

prosti
fonte
25
Compre discos mais rápidos? Dado que cada byte da entrada deve ser inspecionado quanto à sua entrada 0x0A, a E / S é sem dúvida o gargalo.
thrig
2
Se você suspeitar wcde ter sobrecarga demais, tente implementar o seu próprio foreach byte in file: if byte == '\n': linecount++. Se implementado em C ou assembler, acho que não ficará mais rápido, exceto talvez no espaço do kernel em um RTOS com maior prioridade (ou até use uma interrupção para isso - você simplesmente não pode fazer mais nada com o sistema. .. tudo bem, eu discordo ;-))
Murphy
3
E só para ter uma ideia da escala, eu fiz um rápido time wc -l some_movie.aviem um arquivo não armazenado em cache, resultando em 5172672 some_movie.avi -- real 0m57.768s -- user 0m0.255s -- sys 0m0.863s. O que basicamente prova que o @thrig está certo, a E / S prejudica seu desempenho nesse caso.
Murphy
10
A melhor maneira de mostrar que é um gargalo de E / S de disco, faça time wc -l some_large_file_smaller_than_cacheduas vezes em rápida sucessão e veja a rapidez da segunda operação, time wc -l some_large_file_larger_than_cachee veja como o tempo não muda entre as execuções. Para um arquivo de ~ 280 MB aqui, o tempo varia de 1,7 segundos a 0,2 segundos, mas para um arquivo de 2 GB são 14 segundos nas duas vezes.
EightBitTony
1
Quão lento é muito lento para você? O que /usr/bin/time wc -l <file>diz? Qual é o seu hardware? É mais rápido se você executar o comando repetidamente? Nós realmente precisamos de mais informações;)
marcelm

Respostas:

21

Você pode tentar escrever em C:

#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(){
  char buf[BUFSIZ];
  int nread;
  size_t nfound=0;
  while((nread=read(0, buf, BUFSIZ))>0){
    char const* p;
    for(p=buf; p=memchr(p,'\n',nread-(p-buf)); nfound++,p++) {;}
  }
  if(nread<0) { perror("Error"); return 1; }
  printf("%lu\n", nfound);
  return 0;
}

Salvar em, por exemplo, wcl.ccompilar, por exemplo, com gcc wcl.c -O2 -o wcle executar com

<yourFile ./wcl

Isso encontra novas linhas espalhadas em um arquivo de 1 GB no meu sistema em cerca de 370ms (execuções repetidas). (O aumento do tamanho do buffer aumenta um pouco o tempo, o que é esperado - o BUFSIZ deve estar próximo do ideal). Isso é muito comparável aos ~ 380ms que estou obtendo wc -l.

Mmaping me proporciona um tempo melhor de cerca de 280ms , mas é claro que tem a limitação de ser limitado a arquivos reais (sem FIFOS, sem entrada de terminal etc.):

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(){
  struct stat sbuf;
  if(fstat(0, &sbuf)<0){ perror("Can't stat stdin"); return 1; }

  char* buf = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, 0/*stdin*/, 0/*offset*/);
  if(buf == MAP_FAILED){ perror("Mmap error"); return 1; } 

  size_t nread = sbuf.st_size, nfound=0;
  char const* p;
  for(p=buf; p=memchr(p,'\n',nread-(p-buf)); nfound++,p++) {;}

  printf("%lu\n", nfound);
  return 0;
}

Eu criei meu arquivo de teste com:

 $ dd if=/dev/zero of=file bs=1M count=1042 

e adicionou algumas linhas de teste com:

 $ echo >> 1GB 

e um editor hexadecimal.

PSkocik
fonte
Fiquei surpreso com o resultado mmap TBH. Eu achava que o mmaping era mais rápido que a leitura / gravação, mas depois vi alguns benchmarks do linux que mostravam o contrário. Parece que é muito verdade neste caso.
PSKocik 02/03/19
4
O mmap obterá resultados muito melhores no linux, porque mapeará para páginas enormes hoje em dia, e as falhas do TLB são sloooowwwwwww.
jthill
Pode haver algum benefício em ler partes diferentes do arquivo em threads separados (por exemplo, com um forloop OpenMP ), para que algum progresso possa ser feito enquanto um thread estiver parado aguardando a entrada. Mas, por outro lado, isso pode dificultar o agendamento de E / S; portanto, tudo o que posso recomendar é experimentá-lo e medir!
precisa
A read()versão pode se beneficiar da leitura antecipada.
Barmar
1
@TobySpeight Sim, multithreading pode acelerar. Também olhar para a varredura de dois bytes de cada vez através de tabelas de pesquisa de 2 ^ 16 forneceu uma velocidade muito boa da última vez que joguei com ele.
PSKocik
18

Você pode aprimorar a solução sugerida pelo @pskocik reduzindo o número de chamadas para read. Existem muitas chamadas para ler BUFSIZblocos de um arquivo de 1 GB. A abordagem usual para fazer isso é aumentando o tamanho do buffer:

  • apenas por diversão, tente aumentar o tamanho do buffer em um fator de 10. Ou 100. No meu Debian 7, BUFSIZé 8192. No programa original, são 120 mil operações de leitura. Provavelmente, você pode pagar um buffer de entrada de 1Mb para reduzi-lo por um fator de 100.
  • para uma abordagem mais otimizada, os aplicativos podem alocar um buffer do tamanho do arquivo, exigindo uma única operação de leitura. Isso funciona bem o suficiente para arquivos "pequenos" (embora alguns leitores tenham mais de 1 GB na máquina).
  • finalmente, você pode experimentar a E / S mapeada na memória, que lida com a alocação como tal.

Ao comparar as várias abordagens, lembre-se de que alguns sistemas (como o Linux) usam a maior parte da memória não utilizada da sua máquina como cache de disco. Há um tempo (quase 20 anos atrás, mencionado nas vil Perguntas frequentes ), fiquei surpreendido com resultados inesperadamente bons de um algoritmo de paginação (não muito bom) que eu havia desenvolvido para lidar com condições de pouca memória em um editor de texto. Foi-me explicado que funcionava rápido porque o programa estava funcionando com os buffers de memória usados ​​para ler o arquivo e que somente se o arquivo fosse relido ou gravado, haveria uma diferença na velocidade.

O mesmo se aplica a mmap(em outro caso ainda na minha lista de tarefas a incorporar em uma FAQ, um desenvolvedor relatou resultados muito bons em um cenário em que o cache do disco era o motivo real da melhoria). O desenvolvimento de benchmarks leva tempo e cuidado para analisar os motivos do bom (ou ruim) desempenho.

Leitura adicional:

Thomas Dickey
fonte
2
Você está superestimando a influência dos tamanhos de buffer acima de um determinado limite. Normalmente, aumentar o tamanho do buffer para além de 4KB-ish não ajuda muito e pode ser prejudicial, pois pode empurrar o buffer para fora do cache L1. Na minha máquina, o teste com ddbuffers de 1 MB é mais lento que 8 KB. O valor padrão de 8 KB para o wc é realmente escolhido muito bem; ele estará próximo do ideal para uma grande variedade de sistemas.
marcelm