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

cat <nazwapliku> | grep <fraza>

Innym często spotykanym nawykiem jest przekierowanie do polecenia less, celem wyświetlenia jego zawartości

cat <nazwapliku> | less

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

lista_plikow=$(ls *)

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 procesorami Intel 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.

cat vs grep czas wykonywania

cat vs grep uzycie pamieci cat vs grep uzycie procesora

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

for i in * ; do
	cat $i | grep abc #metoda pierwsza
	grep abc $i  #metoda druga
done

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

find –type f <sciezka> | xargs grep <fraza>

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).

petla czas wykonywania

petla uzycie pamieci petla uzycie procesora

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 echo
  • ls – tutaj poza dodatkowym procesem w grę wchodzi również narzut w postaci domyślnego sortowania listy
  • ls –U – to samo co wyżej, z pominięciem sortowania
  • ls * – często popełniany błąd, określany mianem useless use of ls – nie ma potrzeby stosowania obu poleceń jednocześnie, wystarczy użycie gwiazdki
  • find . – podobnie jak ls 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.

katalogi czas wykonywania

katalogi uzycie pamieci katalogi uzycie procesora

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)

find files -type f -exec echo {}  \; | wc –l

oraz, dla porównania, to samo, jedynie zamiast pełnej nazwy pojedynczy znak „x”

find files -type f -exec echo -n a \; | wc –c

Powinno to powodować szybsze przekazanie danych do danej funkcji i tym samym szybsze jej wykonanie (w tym przypadku wc). Sprawdźmy zatem wyniki testu:

potok czas wykonywania

potok uzycie pamieci potok uzycie procesora

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).

Podobne artykuły

  • Greg

    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.

  • ali

    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.

    • RokU

      Zapewne też polecenia i potoki były ulepszane.

  • Witek

    Tytuł zasugerował mi, że chodzi bezsensowne użycie serwera grafiki X :-)

    • RokU

      Jest takie powiedzenie: useless use of cat.

  • uosiu

    Trochę do bani testy, miałem w firmie skrypt, który przepisywałem
    z postaci

    for klient in $(ls *); do
    for usługa in $(ls $klient/*)
    for kraj in $(ls $klient/$usługa/*); do
    etc
    done
    done
    done
    

    Zamiana tego typu kwiatków na
    IFS=/ while read -r klient usluga kraj; do
    etc
    done< <(find ./ -type f)

    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)

  • PiotrM

    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.

    • RokU

      Dla mnie czytelniejsze jest cat plik | polecenie.

  • jkl

    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 ;-)

  • jkl

    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