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

11

Para um arquivo muito grande como 1GB%, owc -l é lento. Temos uma maneira mais rápida de calcular o número de novas linhas de um determinado arquivo?

    
por prosti 01.03.2016 / 22:27

2 respostas

20

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.c , compilar, por exemplo, com gcc wcl.c -O2 -o wcl e executar com

<yourFile ./wcl

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

Mmaping me dá um tempo melhor de aproximadamente 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 adicionamos algumas novas linhas de teste com:

 $ echo >> 1GB 

e um editor hexadecimal.

    
por 01.03.2016 / 23:46
18

Você pode melhorar a solução sugerida pelo @pskocik reduzindo o número de chamadas para read . Há um lote de chamadas para ler BUFSIZ chunks de um arquivo de 1Gb. A abordagem usual para fazer isso é aumentando o tamanho do buffer:

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

Ao fazer o benchmark das várias abordagens, você pode ter em mente que alguns sistemas (como o Linux) usam a maior parte da memória não utilizada da sua máquina como um cache de disco. Um tempo atrás (quase 20 anos atrás, mencionado no vil FAQ ), fiquei intrigado com inesperadamente bom resulta de um algoritmo de paginação (não muito bom) que desenvolvi para lidar com condições de pouca memória em um editor de texto. Foi explicado para mim que ele rodou rápido porque o programa estava trabalhando a partir dos buffers de memória usados para ler o arquivo, e que somente se o arquivo fosse relido ou escrito, haveria uma diferença de 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 de disco era o motivo real de melhoria). O desenvolvimento de benchmarks leva tempo e cuidado para analisar as razões do bom (ou ruim) desempenho.

Leitura adicional:

por 02.03.2016 / 00:35