Potrzebujesz czegoś, co jest szybsze niż „wc -l”


12

W przypadku naprawdę dużego pliku, takiego jak 1 GB, wc -ldzieje się to powoli. Czy mamy szybszy sposób obliczania liczby nowych linii dla określonego pliku?


25
Kup szybsze dyski? Biorąc pod uwagę, że każdy bajt wejścia musi zostać sprawdzony pod kątem jego 0x0Ainessacji, we / wy jest niewątpliwie wąskim gardłem.
thrig

2
Jeśli podejrzewasz, że wcmasz zbyt dużo kosztów ogólnych, możesz spróbować wdrożyć własne foreach byte in file: if byte == '\n': linecount++. Jeśli zaimplementowane w C lub asemblerze, nie sądzę, że przyspieszy, chyba że w przestrzeni jądra na RTOS o najwyższym priorytecie (lub nawet w tym celu użyj przerwania - po prostu nie możesz nic zrobić z systemem. .. w porządku, dygresję ;-))
Murphy

3
I tylko po to, żeby poznać skalę, zrobiłem szybki przegląd time wc -l some_movie.aviniebuforowanego pliku, w wyniku czego 5172672 some_movie.avi -- real 0m57.768s -- user 0m0.255s -- sys 0m0.863s. Co w zasadzie dowodzi, że @thrig ma rację, we / wy zaburza wydajność w tym przypadku.
Murphy

10
Najlepszym sposobem, aby pokazać, że jest to wąskie gardło we / wy dysku, zrób time wc -l some_large_file_smaller_than_cachedwa razy z rzędu szybko i zobacz, jak szybka jest druga operacja, a następnie time wc -l some_large_file_larger_than_cachezobacz, jak czas nie zmienia się między uruchomieniami. W przypadku pliku o wielkości ~ 280 MB czas ten wynosi od 1,7 sekundy do 0,2 sekundy, ale w przypadku pliku 2 GB jest to 14 sekund za każdym razem.
EightBitTony

1
Jak wolne jest dla ciebie zbyt wolne? Co /usr/bin/time wc -l <file>mówi? Jaki masz sprzęt? Czy to szybciej, jeśli uruchomisz polecenie wielokrotnie? Naprawdę potrzebujemy więcej informacji;)
marcelm

Odpowiedzi:


21

Możesz spróbować napisać w 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;
}

Zapisz np., wcl.cSkompiluj np. Z gcc wcl.c -O2 -o wcli uruchom z

<yourFile ./wcl

To powoduje, że nowe linie są posypane plikiem 1 GB w moim systemie w około 370 ms (powtarzane przebiegi). (Zwiększenie rozmiarów buforów nieznacznie wydłuża czas, którego należy się spodziewać - BUFSIZ powinien być bliski optymalnego). Jest to bardzo porównywalne do ~ 380 ms, z którego otrzymuję wc -l.

Mmaping daje mi lepszy czas około 280 ms , ale oczywiście ma ograniczenie polegające na ograniczeniu się do rzeczywistych plików (bez FIFOS, bez wejścia na terminal itp.):

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

Plik testowy utworzyłem za pomocą:

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

i dodał kilka nowych linii testowych z:

 $ echo >> 1GB 

i edytor szesnastkowy.


Byłem zaskoczony wynikiem mmapy TBH. Kiedyś myślałem, że mmaping był szybszy niż odczyt / zapis, ale potem zobaczyłem kilka testów porównawczych z linuksem, które wykazały coś przeciwnego. Wygląda na to, że w tym przypadku jest to bardzo prawdziwe.
PSkocik

4
mmap uzyska znacznie lepsze wyniki w Linuksie, ponieważ w dzisiejszych czasach będzie mapowany na ogromne strony, a brakujące TLB są wolne.
jthill

Odczytywanie różnych części pliku w oddzielnych wątkach (np. Za pomocą forpętli OpenMP ) może przynieść pewne korzyści, dzięki czemu można poczynić pewne postępy, gdy jeden wątek jest zablokowany w oczekiwaniu na dane wejściowe. Ale z drugiej strony może to utrudniać planowanie operacji we / wy, więc wszystko, co mogę polecić, to wypróbować i zmierzyć!
Toby Speight

read()Wersja może korzystać z odczytu z wyprzedzeniem.
Barmar

1
@TobySpeight Tak, wielowątkowość może to przyspieszyć. Również skanowanie dwóch bajtów naraz za pomocą tabel wyszukiwania 2 ^ 16 zapewniło całkiem niezłe przyspieszenie ostatniej gry.
PSkocik

18

Możesz ulepszyć rozwiązanie sugerowane przez @pskocik, zmniejszając liczbę połączeń z read. Istnieje wiele wywołań do odczytu BUFSIZfragmentów z pliku 1 Gb. Zwykłym podejściem do tego jest zwiększenie rozmiaru bufora:

  • dla zabawy spróbuj zwiększyć rozmiar bufora 10 razy. Lub 100. W moim Debianie 7 BUFSIZjest 8192. W przypadku oryginalnego programu jest to 120 tysięcy operacji odczytu. Prawdopodobnie możesz sobie pozwolić na bufor wejściowy 1 Mb, aby zmniejszyć to o współczynnik 100.
  • dla bardziej optymalnego podejścia aplikacje mogą przydzielić bufor tak duży jak plik, co wymaga pojedynczej operacji odczytu. Działa to wystarczająco dobrze w przypadku „małych” plików (choć niektóre czytniki mają na swoim komputerze więcej niż 1 Gb).
  • wreszcie możesz eksperymentować z I / O zamapowanymi w pamięci, które obsługują alokację jako taką.

Podczas porównywania różnych podejść możesz mieć na uwadze, że niektóre systemy (takie jak Linux) wykorzystują większość nieużywanej pamięci komputera jako pamięć podręczną dysku. Jakiś czas temu (prawie 20 lat temu, wspomniane w nikczemnym FAQ ), byłem zaskoczony nieoczekiwanie dobrymi wynikami z (niezbyt dobrego) algorytmu stronicowania, który opracowałem do obsługi warunków niskiej pamięci w edytorze tekstu. Wyjaśniono mi, że działał szybko, ponieważ program działał z buforów pamięci używanych do odczytu pliku, i że tylko jeśli plik zostanie ponownie odczytany lub zapisany, będzie różnica prędkości.

To samo dotyczy mmap(w innym przypadku, który wciąż znajduje się na mojej liście rzeczy do zrobienia w celu włączenia do FAQ, deweloper zgłosił bardzo dobre wyniki w scenariuszu, w którym pamięć podręczna dysku była faktycznym powodem poprawy). Opracowanie testów porównawczych wymaga czasu i staranności, aby przeanalizować przyczyny dobrej (lub złej) wydajności.

Dalsza lektura:


2
Przeceniasz wpływ wielkości buforów powyżej pewnego progu. Zazwyczaj zwiększenie rozmiaru bufora poza 4KB nie pomaga zbytnio i może być szkodliwe, ponieważ może wypchnąć bufor z pamięci podręcznej L1. Na moim komputerze testowanie z ddużyciem buforów 1 MB jest wolniejsze niż 8 KB. Domyślna wartość 8KB dla wc jest właściwie wybrana całkiem dobrze, będzie bliska optymalnej dla szerokiego zakresu systemów.
marcelm
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.