Wydajność pętli a ekspansja

9

Potrzebujesz sugestii ekspertów dotyczących poniższego porównania:

Segment kodu za pomocą pętli:

for file in `cat large_file_list`
do
    gzip -d $file
done

Segment kodu za pomocą prostego rozszerzenia:

gzip -d `cat large_file_list`

Który będzie szybszy? Muszę manipulować dużym zestawem danych.

Leon
źródło
1
Prawidłowa odpowiedź będzie zależeć od czasu potrzebnego do uruchomienia gzipsystemu, liczby plików na liście plików i wielkości tych plików.
Kusalananda
Lista plików będzie zawierać około 1000 - 10000 plików. Rozmiar waha się od niektórych kilobajtów do 500 MB. Nie mam pojęcia, ile czasu zajmuje uruchomienie gzip w moim systemie. jakikolwiek sposób sprawdzić?
Leon
1
Ok, to może również zależeć od długości nazw plików . Jeśli nazwy plików są długie, niektóre systemy mogą generować błąd „zbyt długiej listy argumentów”, jeśli spróbujesz to zrobić bez pętli, ponieważ zastąpienie polecenia spowoduje zbyt długą linię poleceń do wykonania przez powłokę. Jeśli nie chcesz polegać na liczbie plików na liście, po prostu użyj pętli. Czy spędzasz dużo czasu na dekompresji tych plików w porównaniu z innym przetwarzaniem, które będziesz na nich wykonywać?
Kusalananda
Leon spójrz na moje wyniki testu: „arystokrata” jest 20 razy szybszy niż „pętla” w moim ustawieniu.
aby uzyskać szczęśliwe medium między rozpoczęciem procesu a długością wiersza poleceń, użyj czegoś takiego, xargs gzip -d < large_file_listale uważaj na spacje w nazwach plików, być może ztr \\n \\0 large_file_list | xargs -0 gzip -d
w00t

Odpowiedzi:

19

Powikłania

Następujące działania będą działać tylko czasami:

gzip -d `cat large_file_list`

Trzy problemy (w bashwiększości innych powłok podobnych do Bourne'a):

  1. Nie powiedzie się, jeśli w nazwie pliku znajduje się spacja lub znaki nowego wiersza (zakładając, $IFSże nie został zmodyfikowany). Wynika to z podziału słów powłoki .

  2. Może również zawieść, jeśli nazwa pliku zawiera znaki glob-active. Wynika to z faktu, że powłoka zastosuje rozszerzenie nazwy ścieżki do listy plików.

  3. Nie powiedzie się również, jeśli nazwa pliku zaczyna się od -(jeśli POSIXLY_CORRECT=1dotyczy to tylko pierwszego pliku) lub jeśli jest nim jakakolwiek nazwa pliku -.

  4. Nie powiedzie się również, jeśli będzie w nim zbyt wiele nazw plików, aby zmieściły się w jednym wierszu poleceń.

Poniższy kod podlega takim samym problemom jak powyższy kod (z wyjątkiem czwartego)

for file in `cat large_file_list`
do
    gzip -d $file
done

Niezawodne rozwiązanie

Jeśli masz large_file_listdokładnie jedną nazwę pliku w wierszu, a nazwanego pliku -nie ma wśród nich, a jesteś w systemie GNU, użyj:

xargs -rd'\n' gzip -d -- <large_file_list

-d'\n'każe xargstraktować każdy wiersz danych wejściowych jako osobną nazwę pliku.

-rmówi, xargsaby nie uruchamiać polecenia, jeśli plik wejściowy jest pusty.

--mówi, gzipże następujących argumentów nie należy traktować jako opcji, nawet jeśli zaczynają się od -. -sam nadal byłby jednak traktowany jako -zamiast wywoływanego pliku -.

xargsumieści wiele nazw plików w każdym wierszu poleceń, ale nie tyle, że przekroczy limit wiersza poleceń. Zmniejsza to liczbę uruchomień gzipprocesu i dlatego jest tak szybkie. Jest również bezpieczny: nazwy plików będą również chronione przed dzieleniem słów i rozszerzaniem nazw ścieżek .

John1024
źródło
Dziękuję za szczegółową odpowiedź. Rozumiem twoje 3 wymienione problemy. Nazwa pliku jest prosta i nie sprosta tym wyzwaniom, ponieważ lista może pomieścić do 20000. A moje pytanie dotyczy w zasadzie wydajności tych dwóch segmentów. Dzięki.
Leon
1
@Leon forPętla będzie - jak dotąd - najwolniejsza. Pozostałe dwie metody będą bardzo blisko siebie.
John1024
7
Nie odrzucaj też potencjalnych problemów: wiele wielu pytań tutaj na StackExchange jest spowodowanych tym, że dzielenie słów lub rozwijanie nazw ścieżek zdarzyło się ludziom, którzy się tego nie spodziewali.
John1024
5
Zauważ również, że istnieją różnice w czytaniu pliku z xargs: przynajmniej wersja GNU ma --arg-fileopcję (krótka forma -a). Można xargs -a large_file_list -rd'\n' gzip -d zamiast tego zrobić . Skutecznie, nie ma żadnej różnicy, poza tym, że <jest operator powłoki i pozwoliłoby xargsczytać ze standardowego wejścia (które shell „linki” do pliku), a -astałaby xargswyraźnie otworzyć plik w pytaniu
Sergiy Kolodyazhnyy
2
terdon zauważył w innym komentarzu o używaniu paralleldo uruchamiania wielu kopii gzip, ale xargs(przynajmniej GNU) również ma -Pprzełącznik do tego. Na maszynach wielordzeniowych może to mieć znaczenie. Ale możliwe jest również, że i tak dekompresja jest całkowicie związana z operacjami wejścia / wyjścia.
ilkkachu
12

Wątpię, żeby to miało znaczenie.

Użyłbym pętli tylko dlatego, że nie wiem, ile plików znajduje się na liście plików, i nie wiem (ogólnie), czy w nazwach plików znajdują się spacje. Wykonanie podstawienia polecenia, które wygenerowałoby bardzo długą listę argumentów, może spowodować błąd „Zbyt długa lista argumentów”, gdy wygenerowana lista jest zbyt długa.

Moja pętla wyglądałaby

while IFS= read -r name; do
    gunzip "$name"
done <file.list

To dodatkowo pozwoliłoby mi wstawiać polecenia do przetwarzania danych po gunzippoleceniu. W rzeczywistości, w zależności od tego, jakie są dane i co należy z nimi zrobić, może być nawet możliwe ich przetwarzanie bez zapisywania go w pliku:

while IFS= read -r name; do
    zcat "$name" | process_data
done <file.list

(gdzie process_datajest jakiś potok odczytujący nieskompresowane dane ze standardowego wejścia)

Jeśli przetwarzanie danych trwa dłużej niż jego rozpakowanie, pytanie, czy pętla jest bardziej wydajna, czy nie, staje się nieistotne.

Idealnie wolałbym jednak nie wyłączać listy nazw plików i zamiast tego używać wzorca globowania nazw plików, jak w

for name in ./*.gz; do
    # processing of "$name" here
done

gdzie ./*.gzjest jakiś wzór pasujący do odpowiednich plików. W ten sposób nie jesteśmy zależni od liczby plików ani od znaków używanych w nazwach plików (mogą zawierać znaki nowej linii lub inne białe znaki, lub zaczynać od myślników itp.)

Związane z:

Kusalananda
źródło
5

Z tych dwóch, ten z wszystkimi plikami przekazanymi do pojedynczego wywołania gzipprawdopodobnie będzie szybszy, właśnie dlatego, że wystarczy uruchomić gziptylko raz. (To znaczy, jeśli polecenie w ogóle działa, zobacz inne odpowiedzi na zastrzeżenia).

Chciałbym jednak przypomnieć o złotej zasadzie optymalizacji : nie rób tego przedwcześnie.

  1. Nie optymalizuj tego typu rzeczy, zanim zorientujesz się, że to problem.

    Czy ta część programu zajmuje dużo czasu? Cóż, dekompresowanie dużych plików może i i tak będziesz musiał to zrobić, więc odpowiedź może nie być tak łatwa.

  2. Pomiar. Naprawdę, to najlepszy sposób, aby się upewnić.

    Zobaczysz wyniki na własne oczy (lub własny stoper) i będą one dotyczyć Twojej sytuacji, której losowe odpowiedzi w Internecie mogą nie mieć. Umieść oba warianty w skryptach i uruchom time script1.sh, oraz time script2.sh. (Zrób to z listą pustych skompresowanych plików, aby zmierzyć bezwzględną kwotę narzutu).

ilkkachu
źródło
0

Jak szybki jest twój dysk?

To powinno wykorzystać wszystkie twoje procesory:

parallel -X gzip -d :::: large_file_list

Zatem twoim ograniczeniem będzie prawdopodobnie prędkość twojego dysku.

Możesz spróbować dostosować za pomocą -j:

parallel -j50% -X gzip -d :::: large_file_list

Spowoduje to uruchomienie połowy zadań równolegle do poprzedniego polecenia i zmniejszy obciążenie dysku, więc w zależności od dysku może to być szybsze.

Ole Tange
źródło