Podam przykład:
$ timeout 1 yes "GNU" > file1
$ wc -l file1
11504640 file1
$ for ((sec0=`date +%S`;sec<=$(($sec0+5));sec=`date +%S`)); do echo "GNU" >> file2; done
$ wc -l file2
1953 file2
Tutaj możesz zobaczyć, że polecenie yes
zapisuje 11504640
linie w sekundę, podczas gdy ja mogę pisać tylko 1953
linie w 5 sekund przy użyciu bash for
i echo
.
Jak sugerowano w komentarzach, istnieją różne sztuczki, aby uczynić go bardziej wydajnym, ale żadne z nich nie jest zbliżone do prędkości yes
:
$ ( while :; do echo "GNU" >> file3; done) & pid=$! ; sleep 1 ; kill $pid
[1] 3054
$ wc -l file3
19596 file3
$ timeout 1 bash -c 'while true; do echo "GNU" >> file4; done'
$ wc -l file4
18912 file4
Mogą napisać do 20 tysięcy wierszy na sekundę. Można je dodatkowo ulepszyć, aby:
$ timeout 1 bash -c 'while true; do echo "GNU"; done >> file5'
$ wc -l file5
34517 file5
$ ( while :; do echo "GNU"; done >> file6 ) & pid=$! ; sleep 1 ; kill $pid
[1] 5690
$ wc -l file6
40961 file6
To daje nam do 40 tysięcy linii na sekundę. Lepiej, ale wciąż daleko, z yes
którego można napisać około 11 milionów wierszy na sekundę!
Więc jak yes
zapisuje się do pliku tak szybko?
date
jest to nieco ciężkie, a ponadto powłoka musi ponownie otworzyć strumień wyjściowyecho
dla każdej iteracji pętli. W pierwszym przykładzie jest tylko jedno wywołanie polecenia z jednym przekierowaniem wyjścia, a polecenie jest bardzo lekkie. Oba nie są w żaden sposób porównywalne.date
może być ciężki, zobacz edytuj moje pytanie.timeout 1 $(while true; do echo "GNU">>file2; done;)
jest niewłaściwym sposobem użycia,timeout
ponieważtimeout
polecenie uruchomi się dopiero po zakończeniu zastępowania polecenia. Użyjtimeout 1 sh -c 'while true; do echo "GNU">>file2; done'
.write(2)
wywołaniach systemowych, a nie na obciążeniach innymi syscallami, narzutami powłoki, a nawet tworzeniem procesów w pierwszym przykładzie (który działa i czekadate
na każdą linię wydrukowaną w pliku). Jedna sekunda pisania to zaledwie tyle, ile wynosi wąskie gardło we / wy dysku (zamiast procesora / pamięci) w nowoczesnym systemie z dużą ilością pamięci RAM. Gdyby pozwolić na dłuższą pracę, różnica byłaby mniejsza. (W zależności od tego, jak źle używasz implementacji bash oraz względnej prędkości procesora i dysku, możesz nawet nie nasycić dysku we / wy dyskiem bash).Odpowiedzi:
łupina od orzecha:
yes
wykazuje podobne zachowanie do większości innych standardowych narzędzi, które zazwyczaj zapisują do STREAMU PLIKÓW, a dane wyjściowe są buforowane przez libC przez stdio . Są tylko zrobić syscallwrite()
każdy niektóre 4KB (16KB lub 64KB) lub cokolwiek wyjście bloku bufsiz jest.echo
jestwrite()
naGNU
. To jest dużo z trybu przełączania (co nie jest, jak widać, jak kosztowne jako kontekst przełącznika ) .I wcale nie wspominając, że poza początkową pętlą optymalizacji
yes
jest to bardzo prosta, niewielka, skompilowana pętla C, a pętla powłoki nie jest w żaden sposób porównywalna z programem zoptymalizowanym pod kątem kompilatora.ale byłem w błędzie:
Kiedy powiedziałem wcześniej, że
yes
użyłem stdio, założyłem, że tak, ponieważ zachowuje się bardzo podobnie do tych, które to robią. To nie było poprawne - tylko naśladuje ich zachowanie w ten sposób. To, co faktycznie robi, jest bardzo podobne do tego, co zrobiłem poniżej z powłoką: najpierw zapętla się, by złączyć swoje argumenty (luby
jeśli ich nie ma), dopóki nie wzrosną bez przekroczeniaBUFSIZ
.Komentarz ze źródła bezpośrednio poprzedzający odpowiednie
for
stany pętli:yes
write()
potem robi swoje .dygresja:
(Jak pierwotnie uwzględniono w pytaniu i zachowano w kontekście możliwego pouczającego wyjaśnienia już tutaj napisanego) :
timeout
Problem masz z podstawienia polecenia - Chyba się to teraz, a może wyjaśnić, dlaczego nie zatrzymać.timeout
nie uruchamia się, ponieważ jego linia poleceń nigdy nie jest uruchamiana. Twoja skorupa rozwidla powłokę potomną, otwiera rurkę na swoim standardowym wyjściu i czyta ją. Przestanie czytać, gdy dziecko zakończy pracę, a następnie zinterpretuje wszystko, co napisało dziecko dla$IFS
manglingu i ekspansji globu, a rezultatem zastąpi wszystko, od$(
dopasowywania)
.Ale jeśli dziecko jest nieskończoną pętlą, która nigdy nie zapisuje do potoku, to dziecko nigdy nie przestaje zapętlać, a
timeout
linia poleceń nigdy nie jest uzupełniana (jak sądzę) , robiszCTRL-C
i zabijasz pętlę potomną. Dlatego nigdy nietimeout
można zabić pętli, która musi zostać ukończona, zanim będzie mogła się rozpocząć.inne
timeout
s:... po prostu nie są tak istotne dla twoich problemów z wydajnością, jak ilość czasu, którą Twój program powłoki musi poświęcić na przełączanie między trybem użytkownika a trybem jądra, aby obsłużyć dane wyjściowe.
timeout
nie jest jednak tak elastyczny, jak powłoka może być w tym celu: gdzie powłoki wyróżniają się zdolnością do łączenia argumentów i zarządzania innymi procesami.Jak zauważono gdzie indziej, po prostu przeniesienie
[fd-num] >> named_file
przekierowania do celu wyjściowego pętli, a nie tylko kierowanie tam wyjścia dla zapętlonego polecenia, może znacznie poprawić wydajność, ponieważ w ten sposób przynajmniejopen()
syscall musi być zrobiony tylko raz. Odbywa się to również poniżej, gdy|
rura jest kierowana jako wyjście dla wewnętrznych pętli.bezpośrednie porównanie:
Możesz zrobić jak:
To trochę przypomina opisaną wcześniej relację podrzędną polecenia, ale nie ma potoku, a dziecko jest w tle, dopóki nie zabije rodzica. W
yes
przypadku, gdy rodzic faktycznie został zastąpiony od momentu odrodzenia dziecka, ale powłoka wywołujeyes
, nakładając własny proces na nowy, więc PID pozostaje ten sam, a jego dziecko zombie wciąż wie, kogo zabić.większy bufor:
Teraz zobaczmy, jak zwiększyć
write()
bufor powłoki .Wybrałem tę liczbę, ponieważ ciągi wyjściowe dłuższe niż 1kb były
write()
dla mnie dzielone na osobne . I oto znowu pętla:To 300 razy więcej danych zapisanych przez powłokę w tym samym czasie dla tego testu niż w ostatnim. Nieźle. Ale tak nie jest
yes
.związane z:
Zgodnie z prośbą istnieje bardziej dokładny opis niż zwykłe komentarze do kodu dotyczące tego, co zostało zrobione tutaj pod tym linkiem .
źródło
dd
jest standardowym narzędziem, które zdecydowanie nie używa na przykład stdio. większość innych.open
(istnieje)write
ORAZclose
(co, jak sądzę, nadal czeka na opróżnienie), ORAZ tworzy nowy proces i wykonujedate
dla każdej pętli.wc -l
pętlębash
dla mnie dostaje 1/5 dnia wyprowadzeniash
pętli robi -bash
zarządza nieco ponad 100kwrites()
dodash
„s 500k.for((sec0=`date +%S`;...
kontrolą czasu i przekierowaniem w pętli, a nie kolejnymi ulepszeniami.Lepszym pytaniem byłoby, dlaczego powłoka tak wolno zapisuje plik. Każdy samodzielny skompilowany program, który w sposób odpowiedzialny używa syscalls do zapisywania plików (nie opróżniając wszystkich znaków na raz), zrobiłby to dość szybko. To, co robisz, to pisanie wierszy w interpretowanym języku (powłoce), a ponadto wykonujesz wiele niepotrzebnych operacji wprowadzania danych wyjściowych. Co
yes
robi:Co robi twój skrypt:
date
zewnętrzne polecenie i zapisz jego dane wyjściowe (tylko w wersji oryginalnej - w wersji poprawionej zyskujesz współczynnik 10, nie robiąc tego)echo
polecenie, rozpoznaj go (z pewnym kodem dopasowującym wzór) jako wbudowaną powłokę, wywołaj interpretację parametrów i wszystko inne w argumencie „GNU”, a na koniec zapisz wiersz do otwartego plikuDrogie części: cała interpretacja jest niezwykle kosztowna (bash wykonuje strasznie dużo przetwarzania wstępnego wszystkich danych wejściowych - twój łańcuch może potencjalnie zawierać podstawianie zmiennych, podstawianie procesów, rozwijanie nawiasów klamrowych, znaki ucieczki i więcej), każde wywołanie wbudowanego jest prawdopodobnie instrukcja switch z przekierowaniem do funkcji, która zajmuje się wbudowanym programem, i co bardzo ważne, otwierasz i zamykasz plik dla każdego wiersza wyniku. Możesz
>> file
wyłączyć pętlę while, aby uczynić ją znacznie szybszą , ale nadal jesteś w tłumaczonym języku. Masz to szczęścieecho
jest wbudowaną powłoką, a nie zewnętrznym poleceniem - w przeciwnym razie twoja pętla wymagałaby tworzenia nowego procesu (fork & exec) przy każdej iteracji. Co zatrzymałoby proces - zatrzymałeś się - zobaczyłeś, ile to kosztuje, gdy maszdate
polecenie w pętli.źródło
Pozostałe odpowiedzi dotyczyły głównych punktów. Na marginesie, możesz zwiększyć przepustowość pętli while, pisząc do pliku wyjściowego na końcu obliczeń. Porównać:
z
źródło