Hai bisogno di qualcosa che sia più veloce di "wc -l"


12

Per un file molto grande come 1 GB wc -lsembra essere lento. Abbiamo un modo più veloce per calcolare il numero di newline per un determinato file?


25
Acquista dischi più veloci? Dato che ogni byte dell'ingresso deve essere ispezionato per la sua 0x0Ainess, l'I / O è senza dubbio il collo di bottiglia.
thrig

2
Se sospetti wcdi avere un sovraccarico eccessivo, puoi provare a implementare il tuo foreach byte in file: if byte == '\n': linecount++. Se implementato in C o assemblatore, non credo che andrà più veloce, tranne forse nello spazio del kernel su un RTOS con la massima priorità (o anche usando un interrupt per quello - semplicemente non puoi fare nient'altro con il sistema. .. va bene, sto divagando ;-))
Murphy

3
E solo per avere time wc -l some_movie.aviun'idea della scala, ho fatto un rapido giro su un file non cache, risultando 5172672 some_movie.avi -- real 0m57.768s -- user 0m0.255s -- sys 0m0.863s. Il che dimostra sostanzialmente che @thrig ha ragione, in questo caso l'I / O frantuma le prestazioni.
Murphy

10
Il modo migliore per mostrare che è un collo di bottiglia di I / O su disco, eseguire time wc -l some_large_file_smaller_than_cachedue volte in rapida successione e vedere quanto è veloce la seconda operazione, quindi time wc -l some_large_file_larger_than_cachevedere come il tempo non cambia tra le esecuzioni. Per un file di ~ 280 MB qui, il tempo passa da 1,7 secondi a 0,2 secondi, ma per un file da 2 GB è 14 secondi entrambe le volte.
EightBitTony

1
Quanto è troppo lento per te? Cosa /usr/bin/time wc -l <file>dice? Qual è il tuo hardware? È più veloce se si esegue ripetutamente il comando? Abbiamo davvero bisogno di maggiori informazioni;)
marcelm

Risposte:


21

Puoi provare a scrivere in 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;
}

Salva in eg wcl.c,, compila eg, con gcc wcl.c -O2 -o wcled esegui con

<yourFile ./wcl

Questo trova nuove righe cosparse in un file da 1 GB sul mio sistema in circa 370ms (ripetizioni). (L'aumento delle dimensioni del buffer aumenta leggermente il tempo, che è prevedibile - BUFSIZ dovrebbe essere vicino all'ottimale). Questo è molto paragonabile ai ~ 380ms che sto ricevendo wc -l.

Il Mmaping mi offre un tempo migliore di circa 280 ms , ma ovviamente ha il limite di essere limitato ai file reali (niente FIFOS, nessun input terminale, ecc.):

#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;
}

Ho creato il mio file di test con:

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

e aggiunto alcune nuove righe di test con:

 $ echo >> 1GB 

e un editor esadecimale.


Sono stato sorpreso dal risultato mmap TBH. Pensavo che mmaping fosse più veloce di lettura / scrittura, ma poi ho visto alcuni benchmark di Linux che mostravano il contrario. Sembra che sia molto vero in questo caso.
PSkocik

4
mmap otterrà risultati di gran lunga migliori su Linux perché in questi giorni eseguirà il mapping su pagine enormi e i mancati TLB sono sloooowwwwwww.
jthill

Potrebbero esserci dei vantaggi nella lettura di diverse parti del file in thread separati (ad es. Con un forciclo OpenMP ) in modo che alcuni progressi possano essere fatti mentre un thread è bloccato in attesa di input. D'altra parte, potrebbe ostacolare la programmazione degli I / O, quindi tutto ciò che posso raccomandare è provarlo e misurare!
Toby Speight,

La read()versione potrebbe beneficiare di read-ahead.
Barmar

1
@TobySpeight Sì, il multithreading potrebbe accelerarlo. Anche guardare la scansione di due byte alla volta tramite un 2 ^ 16 tavoli di ricerca ha fornito una velocità piuttosto buona l'ultima volta che ci ho giocato.
PSkocik,

18

Puoi migliorare la soluzione suggerita da @pskocik riducendo il numero di chiamate a read. Ci sono molte chiamate per leggere BUFSIZblocchi da un file da 1 GB. L'approccio abituale per farlo è aumentando la dimensione del buffer:

  • solo per divertimento, prova ad aumentare la dimensione del buffer di un fattore di 10. O 100. Sul mio Debian 7, BUFSIZè 8192. Con il programma originale, sono 120 mila operazioni di lettura. Probabilmente puoi permetterti un buffer di input da 1 Mb per ridurlo di un fattore 100.
  • per un approccio più ottimale, le applicazioni possono allocare un buffer grande quanto il file, richiedendo un'unica lettura. Funziona abbastanza bene per file "piccoli" (anche se alcuni lettori hanno più di 1 GB sul proprio computer).
  • infine, è possibile sperimentare l'I / O mappato in memoria, che gestisce l'allocazione in quanto tale.

Quando si confrontano i vari approcci, è necessario tenere presente che alcuni sistemi (come Linux) utilizzano la maggior parte della memoria inutilizzata della macchina come cache del disco. Qualche tempo fa (quasi 20 anni fa, menzionato nella vile FAQ ), ero perplesso dai risultati sorprendentemente buoni di un algoritmo di paging (non molto buono) che avevo sviluppato per gestire le condizioni di memoria insufficiente in un editor di testo. Mi è stato spiegato che ha funzionato velocemente perché il programma funzionava dai buffer di memoria utilizzati per leggere il file e che solo se il file fosse stato riletto o scritto avrebbe avuto una differenza di velocità.

Lo stesso vale per mmap(in un altro caso ancora nella mia lista di cose da fare da incorporare in una FAQ, uno sviluppatore ha riportato ottimi risultati in uno scenario in cui la cache del disco è stata la vera ragione del miglioramento). Lo sviluppo di parametri di riferimento richiede tempo e cura per analizzare le ragioni della buona (o cattiva) performance.

Ulteriori letture:


2
Stai sopravvalutando l'influenza delle dimensioni del buffer al di sopra di una determinata soglia. In genere, aumentare la dimensione del buffer oltre 4KB-ish non aiuta molto, e in effetti potrebbe essere dannoso perché potrebbe espellere il buffer dalla cache L1. Sulla mia macchina, il test con dd, usando buffer da 1 MB è più lento di 8 KB. Il valore predefinito di 8 KB per wc è in realtà scelto piuttosto bene, sarà vicino all'ottimale per una vasta gamma di sistemi.
marcelm
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.