Useless Use of X to określenie stosowane dla sytuacji, kiedy użycie polecenia X (np. cat
czy echo
) jest zbędne – bo istnieje możliwość użycia lepszego do tego zadania narzędzia (albo żadnego dodatkowego). Przykładowo według definicji na stronie gnu.org głównym przeznaczeniem polecenia cat
jest konkatenacja (łączenie) dwóch lub więcej strumieni (np. plików) w jeden ciąg znaków a następnie skierowanie ich na standardowe wyjście. Z tego powodu użycie polecenia cat
dla jednego tylko parametru jest w większości przypadków marnotrawieniem zasobów i niepotrzebnym wykonywaniem dodatkowego procesu. Dopuszcza się jednak użycie polecenia cat
do wyświetlenia zawartości pliku na ekran.
Klasycznym przykładem jest użycie cat
dla pliku I przekierowanie strumieniem do polecenia grep
[bash]cat <nazwapliku> | grep <fraza>[/bash]
Innym często spotykanym nawykiem jest przekierowanie do polecenia less
, celem wyświetlenia jego zawartości
[bash]cat <nazwapliku> | less[/bash]
Istnieje małe prawdopodobieństwo, że używane przez nas narzędzie nie posiada możliwości wczytywania danych z pliku, co więcej większość z nich wymaga podania go jako parametru, jeśli uruchamiamy narzędzie samodzielnie (a nie przez potok).
Inny przykład dotyczy użycia poleceń ls
, czy find
dla listowania zawartości katalogu
[bash]lista_plikow=$(ls *)[/bash]
gdzie problemem jest niepotrzebne wykorzystanie symbolu gwiazdki oraz zapominanie o domyślnym sortowaniu listy na wyjściu, (które również może spowalniać pracę).
Są to trywialne przykłady, które obrazują rodzaj problemu, z jakim mamy do czynienia. Na pierwszy rzut oka narzut, jaki mogą powodować to, co jedynie zmniejszenie czytelności kodu (co również jest kwestią sporną, są zwolennicy takich właśnie składni). Podobnych przykładów jest więcej i nie dotyczą one tylko cat
czy ls
– udokumentowano zjawiska w postaci zbędnego użycia poleceń test
, wc
czy kill -9
. Każde z nich posiada jakiś narzut na wykonywany kod, czy to wydajnościowy, czy po prostu zmniejszający czytelność.
Nasuwać się może pytanie – jak duże są różnice w szybkości wykonywanych programów dla przedstawionych sytuacji. Postanowiliśmy sprawdzić ten oraz inne przykłady poddając testom różne warianty tych samych rozwiązań.
Metodologia testów:
W celu sprawdzenia różnic wydajnościowych przygotowaliśmy następujące środowisko testowe:
- Serwer
IBM xSeries 346
z dwoma fizycznymi procesoramiIntel Xeon 3.0Ghz
- 3072 MB pamięci RAM DDR2 ECC
- 6 dysków SCSI, 15k obrotów na sekundę każdy, ustawione w sprzętowy RAID 5 (przybliżone transfery ~350MB/s odczyt, 90MB/s zapis)
Pomiary wykonywano na próbce 55 000 plików, 5000 o rozmiarze 1MB oraz 50000 o rozmiarze 1KB. Każdy z nich zapełniono danymi znakowymi, po 100 znaków na linię (w przybliżeniu 10 000 linii w większym oraz 10 linii w mniejszym pliku)
Porada: Operację generowania tekstowych plików można osiągnąć w następujący sposób:
dd if=/dev/urandom bs=1024 count=4096 | tr -dc a-zA-Z0-9 | sed -e "s/.\{100\}/&\n/g" > plik.txt
Z uwagi na ograniczenia jakie narzucał sprzętowy kontroler RAID (brak możliwości modyfikacji parametrów cache narzędziem hdparm
) przed każdym testem czyszczono pamięć operacyjną ze zbuforowanych plików (poleceniem sysctl vm.drop_caches=3
). Ma to na celu zapobiegnięcie nieprawidłowościom w wynikach następujących po sobie testów.
Do pomiaru informacji o czasie wykonywania, użycia procesora oraz pamięci wykorzystano narzędzie GNU time.
Test 1: cat vs grep
Pierwszy z testów to przedstawiony już trywialny przykład nadużycia polecenia cat
. Porównujemy tutaj czas jakie potrzebowało każde z poleceń na ukończenie zadania, ilość użytej pamięci oraz obciążenie procesora. Test przeprowadzono na wymienionej wyżej próbce plików, parsując po kolei zawartość każdego z nich.
Jak widać różnice nie są tak znaczące jak mogło by się wydawać. Ilość użytej pamięci jest dokładnie taka sama, procent użycia procesora jest nieznacznie większy w przypadku samego polecenia grep
. Różnica czasowa stanowi w przybliżeniu 1%, tj grep
samodzielnie wykonuje się około 1% szybciej niż wersja wspierana poleceniem cat
i potokiem.
Test 2: Użycie pętli dla dużej liczby plików
W drugim teście porównujemy dwa powyższe przykłady (cat
i grep
) dorzucając do tego element pętli. W poprzednim przypadku występowało ograniczenie narzucane przez system w postaci liczby parametrów jakie maksymalnie mogło przyjąć dane polecenie(w zależności od środowiska może to być np. 65 tysięcy). Przykładowo mając katalog zawierający kilkaset tysięcy plików użycie gwiazdki mogłoby nie zadziałać. Wielu początkujących programistów posłużyłoby się w takim przypadku pętlą, zapominając o znacznie wydajniejszych alternatywach (o których za chwilę).
Struktura testowanej pętli jest następująca
[bash]for i in * ; do
cat $i | grep abc #metoda pierwsza
grep abc $i #metoda druga
done[/bash]
Przetestujemy także ten sam skrypt ale zrealizowany przy użyciu xargs
(przypomnijmy – problemem jest tutaj zbyt duża liczba plików w katalogu których nie można podać jako parametry). Składnia polecenia korzystająca z xargs
jest następująca
[bash]find –type f <sciezka> | xargs grep <fraza>[/bash]
Zamiast find można użyć nieco szybszego ls –U
, pamiętając jednak że polecenie xargs
potrzebuje pełnej ścieżki do plików, (w odniesieniu do miejsca uruchomienia skryptu).
Tak jak w poprzednim teście, różnice czasowe pomiędzy cat
a grep
były dość niewielkie, wahające się w granicach 3%. Znacznie różni się natomiast użycie procesora dla tych dwóch przypadków – w przypadku pętli wywołującej samo polecenie grep
użycie CPU było ponad połowę mniejsze niż przy połączeniu cat
i grep
potokiem. Nietrudno zatem domyślić się że gdyby mocy obliczeniowej zabrakło to właśnie procesor stałby się „wąskim gardłem” a drugi z testów wykonałby się zdecydowanie dłużej.
Powodem dla wystąpienia takiej różnicy jest problem opisywany wcześniej – pętla dla każdego pliku w katalogu tworzyła dwa nowe procesy zamiast jednego. Rezultat – w porównaniu do poprzedniego testu ten wydłużył się o prawie 35%, mimo iż był wykonywany na tej samej próbce danych (55 tysięcy plików).
Różnica między pętlą a xargs
z kolei widoczna jest gołym okiem – zarówno czasowo jak i pod względem narzutu w obliczeniach. Wniosek? Należy unikać sytuacji kiedy tworzymy wiele nowych procesów i szukać alternatywnych rozwiązań w postaci np. xargs
.
Test 3 – wyświetlanie zawartości katalogu
W tym teście pokazujemy inny przykład Useless use of X – różne metody na wyświetlenie zawartości katalogu. Przetestowaliśmy następujące warianty
echo *
– czyli gwiazdka rozwiązywana bezpośrednio przez interpreter. Należy przy tym pamiętać by nie popełniać niepotrzebnie błędu useless use of echo – gwiazdka może samodzielnie odpowiadać liście plików, jedynie aby wypisać ją na ekran trzeba posiłkować się poleceniem echols
– tutaj poza dodatkowym procesem w grę wchodzi również narzut w postaci domyślnego sortowania listyls –U
– to samo co wyżej, z pominięciem sortowanials *
– często popełniany błąd, określany mianem useless use of ls – nie ma potrzeby stosowania obu poleceń jednocześnie, wystarczy użycie gwiazdkifind .
– podobnie jakls
wyjście jest sortowane oraz każdy plik jest wypisywany w nowej linii
Powyższe polecenia można wykorzystać aby przekazać wiele parametrów do jednego polecenia lub stworzyć pętlę operującą na każdym z plików ( for $i in ...
).
Do tego testu zwiększyliśmy wielkość katalogu – z 50 tysięcy do 500 tysięcy małych plików.
Jak widać znajomość dostępnych w danym poleceniu przełączników oraz zasady ich działania zdecydowanie się przydaje. Szczególnie w przypadku narzędzia ls
, stosując proste sztuczki możemy przyśpieszyć pracę programu nawet kilkakrotnie. Dobitnie widać to na dużej liczbie plików – czas potrzebny na posortowanie ich nazw rośnie nieliniowo (w mniejszym lub większym stopniu, w zależności od użytego algorytmu). W przypadku polecenia ls zrezygnowanie z sortowania oszczędza nie tylko czas ale również moc obliczeniową (30% mniejszy narzut) oraz ilość pamięci operacyjnej (brak sortowania oznacza też brak konieczności alokacji pamięci dla dużych ilości danych).
Polecenie ls *
jest natomiast często popełnianym przez programistów błędem i zyskało osobny przydomek Useless use of ls. Na tak kiepski wynik składa się kilka elementów:
- narzut w postaci sortowania (widoczny przy samym poleceniu
ls
) - narzut spowodowany użyciem gwiazdki (widoczny częściowo w przykładzie
echo *
)
Test 4 – wpływ potoku na wydajność
Postanowiliśmy sprawdzić również jak duży narzut może mieć przekazywanie różnej wielkości danych przez potok do innego polecenia. W tym celu posłużyliśmy się poleceniem find
z parametrem exec
, który dla każdego znalezionego pliku przekazywał będzie do polecenia wc
pełną nazwę pliku (w przybliżeniu 10 znaków)
[bash]find files -type f -exec echo {} \; | wc –l[/bash]
oraz, dla porównania, to samo, jedynie zamiast pełnej nazwy pojedynczy znak „x”
[bash]find files -type f -exec echo -n a \; | wc –c[/bash]
Powinno to powodować szybsze przekazanie danych do danej funkcji i tym samym szybsze jej wykonanie (w tym przypadku wc
). Sprawdźmy zatem wyniki testu:
Jak widać różnica jest bardzo znikoma. Długi czas wykonywania samego testu wynikała głównie z powodu użycia funkcji exec
polecenia find
. Każdorazowe jego wykonanie powodowało narzut obliczeniowy przy tworzeniu nowego procesu (echo
) dla każdego z plików. Pomijając ten efekt nie widać żadnego narzutu wywołanego domniemanym „wąskim gardłem” w postaci potoku.
Podsumowanie
Z powyższych testów można wyciągnąć co najmniej dwie nauki. Pierwsza dotyczy samego Useless use of X oraz ewentualnego narzutu na obliczenia jaki może powodować. Jak wynikało z na wykresów wpływ ten jest bardzo zróżnicowany i zależny od rodzaju i sposobu użycia danego polecenia. Pojedyncze nadużycie polecenia cat
czy echo
w niewielkim stopniu przekłada się na czas wykonywania samego programu (różnica rzędu kilku procent), miałoby ono jednak znaczenie jeśli ten proces byłby wielokrotnie powtarzany.
Elementem który najbardziej wpływa na wydajność i który najczęściej pojawia się w dokumentacji useless use of X jest zatem niepotrzebne tworzenie procesów. Jest to element którego należy się wystrzegać, ponieważ potrzebuje dużej ilości obliczeń (a co za tym idzie czasu do wykonania) zanim docelowe polecenie będzie mogło się wykonać. Widać to szczególnie na dużej liczbie wywołań –w pętli ten sam algorytm wykonywał się prawie 35% dłużej niż przy podaniu wszystkich argumentów w momencie uruchamiania. Należy zatem unikać takich sytuacji i szukać rozwiązań alternatywnych (np. w postaci xargs
gdy plików jest za dużo by podać je jako parametr).
Dzięki za testy, które potwierdziły to co myślałem. Wbrew opiniom można pisać skrypty jak się chce bo nie ma to większego narzutu na wydajność basha i systemu.
Jakbyśmy żyli w erze 386 albo nawet wcześniejszej to te testy miałyby może jakiś sens, ale w dobie dzisiejszych komputerów po 4/8 rdzeni i gigabajtów ram różnice w wykonywaniu nie mają większego znaczenia.
Zapewne też polecenia i potoki były ulepszane.
Tytuł zasugerował mi, że chodzi bezsensowne użycie serwera grafiki X :-)
Jest takie powiedzenie: useless use of cat.
Trochę do bani testy, miałem w firmie skrypt, który przepisywałem
z postaci
[code]
for klient in $(ls *); do
for usługa in $(ls $klient/*)
for kraj in $(ls $klient/$usługa/*); do
etc
done
done
done
[/code]
Zamiana tego typu kwiatków na
[code][/code]IFS=/ while read -r klient usluga kraj; do
etc
done< <(find ./ -type f)
[code][/code]
Skróciła czas wykonywania skryptu z 10 na 3 sekundy, przy czym użycie cpu wzrosło z 3% do 78%. Maszyna to i7 quad+HT z plikami w tmpfs (ddr3 1333)
Pomijanie poleceń cat czy echo owszem, może zmniejszyć wydajność ale skrypty powłoki nie służą wydajności. Powinny być stosowane jako kod pomocniczy, szybki w tworzeniu i czytelny. Fraza costampolecenie jakisplik jest mniej czytelna niż: cat jakisplik | costampolecenie. Kod powinien mówić pełnymi zdaniami, jak pani w szkole każe, a nie: no, ehę, itp.
Dla mnie czytelniejsze jest cat plik | polecenie.
A nikt Wam nie powiedzial, ze branie nazw plikow za pomoca 'ls' to zbrodnia? lamery… :///
A czy Ty przeczytałeś cały artykuł do końca? Przecież o to w nim właśnie chodziło ;-)
Faktycznie, ale ze mnie lamer ;) Popatrzylem, ze o jakims sortowaniu, nie o EOL przy ls i zrobilem z siebie debila, sorry :>
Rece mam szybsze od glowy ;/
To wykorzystaj je do zadowalania kobiety a nie pisania komentarzy :P Więcej będzie z tego pożytku :D