Porównuję następujące
tail -n 1000000 stdout.log | grep -c '"success": true'
tail -n 1000000 stdout.log | grep -c '"success": false'
z następującymi
log=$(tail -n 1000000 stdout.log)
echo "$log" | grep -c '"success": true'
echo "$log" | grep -c '"success": false'
i zaskakująco drugi zajmuje prawie 3 razy dłużej niż pierwszy. Powinno być szybciej, prawda?
bash
performance
io
phunehehe
źródło
źródło
$( command substitution )
jest przesyłane strumieniowo. Cała reszta odbywa się równolegle przez potoki, ale w drugim przykładzie musisz poczekać na zakończenie. Wypróbuj z << TUTAJ \ n $ {log = $ (polecenie)} \ nHERE - zobacz, co otrzymujesz.log=
grep
dla, możesz zauważyć pewne przyspieszenie,tee
więc plik jest zdecydowanie odczytywany tylko raz.cat stdout.log | tee >/dev/null >(grep -c 'true'>true.cnt) >(grep -c 'false'>false.cnt); cat true.cnt; cat false.cnt
tail -n 10000 | fgrep -c '"success": true'
i false.Odpowiedzi:
Z jednej strony pierwsza metoda wywołuje
tail
dwa razy, więc musi wykonać więcej pracy niż druga metoda, która robi to tylko raz. Z drugiej strony, druga metoda musi skopiować dane do powłoki, a następnie wycofać, więc musi wykonać więcej pracy niż pierwsza wersja, do którejtail
jest bezpośrednio podłączonagrep
. Pierwszy sposób ma dodatkową korzyść w maszynie wieloprocesorowym:grep
może pracować równolegletail
, przy czym drugi sposób jest ściśle odcinkach, po pierwszetail
, po czymgrep
.Nie ma więc oczywistego powodu, dla którego jeden powinien być szybszy od drugiego.
Jeśli chcesz zobaczyć, co się dzieje, sprawdź, jakie wywołania systemowe wykonuje powłoka. Spróbuj także z różnymi powłokami.
W metodzie 1 głównymi etapami są:
tail
czyta i szuka punktu wyjścia.tail
zapisuje fragmenty o długości 4096 bajtów, któregrep
odczytują tak szybko, jak są tworzone.W metodzie 2 główne etapy to:
tail
czyta i szuka punktu wyjścia.tail
zapisuje fragmenty o długości 4096 bajtów, które bash odczytuje jednocześnie 128 bajtów, a zsh odczytuje jednocześnie 4096 bajtów.grep
odczytują tak szybko, jak są tworzone.128-bajtowe fragmenty Basha podczas czytania wyniku podstawienia polecenia znacznie go spowalniają; Zsh wychodzi dla mnie tak szybko jak metoda 1. Twój przebieg może się różnić w zależności od rodzaju i liczby procesorów, konfiguracji harmonogramu, wersji zaangażowanych narzędzi i wielkości danych.
źródło
st_blksize
wartość potoku, która wynosi 4096 na tym komputerze (i nie wiem, czy to dlatego, że jest to rozmiar strony MMU). Bash 128 musiałby być wbudowaną stałą.Zrobiłem następujący test i w moim systemie wynikowa różnica jest około 100 razy dłuższa dla drugiego skryptu.
Mój plik jest wyjściem strace o nazwie
bigfile
Skrypty
Właściwie nie mam żadnych dopasowań dla grep, więc nic nie jest zapisywane do ostatniego potoku do
wc -l
Oto czasy:
Uruchomiłem więc dwa skrypty ponownie za pomocą polecenia strace
Oto wyniki ze śladów:
I p2.strace
Analiza
Nic dziwnego, że w obu przypadkach większość czasu spędza się na oczekiwaniu na zakończenie procesu, ale p2 czeka 2,63 razy dłużej niż p1, a jak wspomnieli inni, zaczynasz późno w p2.sh.
Więc teraz zapomnij o
waitpid
, zignoruj%
kolumnę i spójrz na kolumnę sekund na obu śladach.Największy czas p1 spędza większość czasu na czytaniu prawdopodobnie zrozumiale, ponieważ jest duży plik do odczytu, ale p2 spędza 28,82 razy dłużej w czytaniu niż p1. -
bash
nie spodziewa się odczytać tak dużego pliku do zmiennej i prawdopodobnie odczytuje bufor jednocześnie, dzieląc go na linie, a następnie otrzymując inny.liczba odczytów p2 wynosi 705k vs 84k dla p1, każdy odczyt wymaga przełączenia kontekstu w przestrzeń jądra i ponownie. Prawie 10-krotna liczba odczytów i przełączników kontekstu.
Czas zapisu p2 spędza 41,93 razy dłużej na zapisie niż p1
liczba zapisów p1 robi więcej zapisów niż p2, 42k vs 21k, jednak są one znacznie szybsze.
Prawdopodobnie z powodu
echo
linii dogrep
w przeciwieństwie do buforów zapisu ogona.Co więcej , p2 spędza więcej czasu na pisaniu niż na czytaniu, p1 jest na odwrót!
Inny czynnik Spójrz na liczbę
brk
wywołań systemowych: p2 wydaje 2,42 razy dłużej na łamanie niż na czytanie! W p1 (nawet się nie rejestruje).brk
dzieje się, gdy program musi rozszerzyć przestrzeń adresową, ponieważ początkowo nie przydzielono wystarczającej ilości miejsca, prawdopodobnie wynika to z bashu konieczności odczytu tego pliku do zmiennej i nie spodziewania się, że będzie on tak duży, i jak wspomniał @scai, jeśli plik staje się zbyt duży, nawet to nie działałoby.tail
jest prawdopodobnie dość wydajnym czytnikiem plików, ponieważ właśnie do tego został przeznaczony, prawdopodobnie mapuje plik i skanuje pod kątem przerwania linii, umożliwiając w ten sposób jądrze zoptymalizowanie operacji wejścia / wyjścia. bash nie jest tak dobry na czas spędzony na czytaniu i pisaniu.p2 spędza 44 ms i 41 ms
clone
iexecv
nie jest to wymierna ilość dla p1. Prawdopodobnie bash czyta i tworzy zmienną z ogona.Wreszcie Totals p1 wykonuje ~ 150 tys. Wywołań systemowych w porównaniu z p2 740 tys. (4,93 razy więcej).
Eliminując oczekiwanie, p1 spędza 0,014416 sekund na wykonywaniu wywołań systemowych, p2 0,439132 sekund (30 razy dłużej).
Wygląda więc na to, że p2 spędza większość czasu w przestrzeni użytkownika, nie robiąc nic poza czekaniem na zakończenie wywołań systemowych i zreorganizowaniem pamięci przez jądro, p1 wykonuje więcej zapisów, ale jest bardziej wydajny i powoduje znacznie mniejsze obciążenie systemu, a zatem jest szybszy.
Wniosek
Nigdy nie próbowałbym się martwić kodowaniem przez pamięć podczas pisania skryptu bash, co nie znaczy, że nie próbujesz być wydajny.
tail
jest zaprojektowany do robienia tego, co robi, prawdopodobnie jestmemory maps
to plik, dzięki czemu jest efektywny w czytaniu i pozwala jądru zoptymalizować operacje we / wy.Lepszym sposobem na zoptymalizowanie problemu może być najpierw
grep
„sukces”: „linie”, a następnie liczyć trues i falses,grep
ma opcję zliczania, która ponownie pozwala uniknąćwc -l
, a nawet lepiej, przepuszczać ogonawk
i liczyć true i załamuje się jednocześnie. p2 nie tylko długo trwa, ale dodaje obciążenie do systemu, gdy pamięć jest tasowana przy pomocy brk.źródło
Właściwie pierwsze rozwiązanie również odczytuje plik do pamięci! Nazywa się to buforowaniem i jest automatycznie wykonywane przez system operacyjny.
I jak już poprawnie wyjaśniono przez mikeserv, pierwsze rozwiązanie sprawdza się
grep
podczas odczytywania pliku, podczas gdy drugie rozwiązanie wykonuje go po odczytaniu plikutail
.Pierwsze rozwiązanie jest szybsze z powodu różnych optymalizacji. Ale nie zawsze musi to być prawda. W przypadku naprawdę dużych plików, których system operacyjny postanawia nie buforować, drugie rozwiązanie może stać się szybsze. Pamiętaj jednak, że w przypadku jeszcze większych plików, które nie zmieszczą się w pamięci, drugie rozwiązanie w ogóle nie będzie działać.
źródło
Myślę, że główna różnica polega po prostu na
echo
powolności. Rozważ to:Jak widać powyżej, czasochłonnym krokiem jest wydruk danych. Jeśli po prostu przekierujesz do nowego pliku i przejrzysz go, jest to o wiele szybsze, gdy czytasz plik tylko raz.
I zgodnie z prośbą, z ciągiem tutaj:
Ten jest nawet wolniejszy, prawdopodobnie dlatego, że ciąg tutaj łączy wszystkie dane w jedną długą linię, co spowolni
grep
:Jeśli zmienna jest cytowana, aby nie nastąpiło dzielenie, rzeczy są nieco szybsze:
Ale wciąż powolny, ponieważ krokiem ograniczającym szybkość jest drukowanie danych.
źródło
<<<
, byłoby interesujące zobaczyć, czy to robi różnicę.Próbowałem też tego ... Najpierw zbudowałem plik:
Jeśli sam wykonasz powyższe, powinieneś wymyślić 1,5 miliona linii
/tmp/log
ze stosunkiem"success": "true"
linii 2: 1 do"success": "false"
linii.Następną rzeczą, którą zrobiłem, było przeprowadzenie testów. Wszystkie testy przeprowadziłem przez serwer proxy,
sh
więctime
musiałbym tylko obserwować jeden proces - i dlatego mogłem pokazać jeden wynik dla całego zadania.Wydaje się to być najszybsze, mimo że dodaje drugi deskryptor pliku i
tee,
myślę, że potrafię wyjaśnić, dlaczego:Oto twój pierwszy:
A twój drugi:
Widać, że w moich testach było więcej niż 3 * różnica prędkości podczas wczytywania go do zmiennej, tak jak ty.
Myślę, że część tego polega na tym, że zmienna powłoki musi być dzielona i obsługiwana przez powłokę podczas odczytu - nie jest to plik.
Z
here-document
drugiej strony, dla wszystkich celów i celów, jestfile
- ifile descriptor,
tak. I jak wszyscy wiemy - Unix działa z plikami.Najbardziej interesujące jest dla mnie
here-docs
to, że możesz nimi manipulowaćfile-descriptors
- prosto|pipe
- i wykonywać je. Jest to bardzo przydatne, ponieważ pozwala ci nieco więcej swobodnie wskazywać,|pipe
gdzie chcesz.Musiałem ponieważ pierwsze zjada i nie ma nic do drugiego czytania. Ale odkąd go włączyłem i podniosłem, żeby przejść do niego, nie miało to większego znaczenia. Jeśli korzystasz z tylu innych, polecam:
tee
tail
grep
here-doc |pipe
|piped
/dev/fd/3
>&1 stdout,
grep -c
Jest jeszcze szybszy.
Ale kiedy uruchamiam go bez
. sourcing
opcji,heredoc
nie mogę z powodzeniem uruchomić pierwszego procesu, aby uruchomić je w pełni jednocześnie. Oto bez pełnego tła:Ale kiedy dodam
&:
Mimo to różnica wydaje się wynosić zaledwie kilka setnych sekundy, przynajmniej dla mnie, więc weź ją tak, jak chcesz.
W każdym razie powodem, dla którego działa szybciej,
tee
jest to, że obagreps
działają w tym samym czasie z jednym wywołaniemtail. tee
duplikatu pliku dla nas i dzieli go na drugigrep
proces wszystkie w strumieniu - wszystko działa od początku do końca, więc wszystko kończy się w tym samym czasie.Wracając do pierwszego przykładu:
A twój drugi:
Ale kiedy podzielimy nasze dane wejściowe i uruchomimy nasze procesy jednocześnie:
źródło