Dlaczego polecenie „znajdź | grep „nazwa pliku” ”o wiele wolniej niż„ znaleźć ”nazwę pliku?

10

Próbowałem obu poleceń, a polecenie find | grep 'filename' jest wiele razy wolniejsze niż proste find 'filename' polecenie.

Jakie byłoby właściwe wytłumaczenie tego zachowania?

yoyo_fun
źródło
2
Wyświetlasz listę wszystkich plików z funkcją find, a następnie przekazujesz dane do grep w celu przetworzenia. Dzięki własnemu findowi brakuje kroku przekazywania każdego wymienionego pliku do grep w celu parsowania wyniku. Będzie to zatem szybsze.
Raman Sailopal
Wolniej w jakim sensie? Czy wykonanie poleceń zajmuje inną ilość czasu?
Kusalananda
1
Nie mogę tego odtworzyć lokalnie. Jeśli cokolwiek, time find "$HOME" -name '.profile'zgłasza dłuższy czas niż time find "$HOME" | grep -F '.profile'. (17s vs. 12s).
Kusalananda
2
@JenniferAnderson Uruchomiłem oba wielokrotnie. Średnie wartości 17 i 12 sekund. I tak, grepodmiana będzie pasować w dowolnym miejscu findwyniku, podczas gdy dopasowanie z find -namepasowałoby tylko dokładnie (w tym przypadku).
Kusalananda
2
Tak, find filename byłoby szybko . Przyjąłem, że to była literówka i że OP miał na myśli find -name filename. Z find filename, tylko filenamezostanie zbadane (i nic więcej).
Kusalananda

Odpowiedzi:

11

(Zakładam, że GNU findtutaj)

Używam tylko

find filename

byłoby szybkie, ponieważ zwróciłoby się po prostu filename, lub nazwy w środku, filenamejeśli jest to katalog, lub błąd, jeśli ta nazwa nie istniała w bieżącym katalogu. Jest to bardzo szybka operacja, podobna do ls filename(ale rekurencyjna, jeśli filenamejest katalogiem).

W przeciwieństwie,

find | grep filename

pozwoli findwygenerować listę wszystkich nazw z bieżącego katalogu i poniżej, które grepnastępnie zostaną odfiltrowane. Byłoby to oczywiście znacznie wolniejsze działanie.

Zakładam, że to, co faktycznie było zamierzone, było

find . -type f -name 'filename'

Będzie to wyglądało filenamejak nazwa zwykłego pliku w dowolnym miejscu w bieżącym katalogu lub poniżej.

Będzie to tak szybkie (lub porównywalnie szybkie), jak find | grep filename, ale greprozwiązanie będzie pasować filenamedo pełnej ścieżki każdej znalezionej nazwy, podobnie do tego -path '*filename*', co by to zrobiło find.


Zamieszanie wynika z niezrozumienia sposobu finddziałania.

Narzędzie pobiera wiele ścieżek i zwraca wszystkie nazwy poniżej tych ścieżek.

Następnie możesz ograniczyć zwracane nazwy za pomocą różnych testów, które mogą oddziaływać na nazwę pliku, ścieżkę, znacznik czasu, rozmiar pliku, typ pliku itp.

Kiedy powiesz

find a b c

poprosisz findo podanie wszystkich nazw dostępnych w ramach trzech ścieżek a, boraz c. Jeśli są to nazwy zwykłych plików w bieżącym katalogu, zostaną one zwrócone. Jeśli którykolwiek z nich jest nazwą katalogu, zostanie on zwrócony wraz ze wszystkimi dalszymi nazwami w tym katalogu.

Kiedy robię

find . -type f -name 'filename'

Generuje to listę wszystkich nazw w bieżącym katalogu ( .) i poniżej. Następnie ogranicza nazwy do zwykłych plików, tj. Nie katalogów itp -type f. Za pomocą . Następnie istnieje dalsze ograniczenie nazw, które pasują filenameprzy użyciu -name 'filename'. Ciąg filenamemoże być wzorcem globowania nazw plików, takim jak *.txt(pamiętaj, żeby go zacytować!).

Przykład:

Następujące zdaje się „znajdować” plik wywołany .profilew moim katalogu domowym:

$ pwd
/home/kk
$ find .profile
.profile

Ale w rzeczywistości zwraca wszystkie nazwy na ścieżce .profile(jest tylko jedna nazwa, i to z tego pliku).

Następnie cdprzechodzę na wyższy poziom i próbuję ponownie:

$ cd ..
$ pwd
/home
$ find .profile
find: .profile: No such file or directory

findKomenda teraz nie mogę znaleźć żadnej ścieżki nazwie .profile.

Jeśli jednak popatrzę na bieżący katalog, a następnie ograniczę tylko zwrócone nazwy.profile , znajdzie je również stamtąd:

$ pwd
/home
$ find . -name '.profile'
./kk/.profile
Kusalananda
źródło
1
find filenamezwróci tylko, filenamejeśli filenamenie byłby katalogiem typu (lub byłby katalogiem typu, ale sam nie miał żadnego wpisu)
Stéphane Chazelas
2

Wyjaśnienie nietechniczne: Szukanie Jacka w tłumie jest szybsze niż szukanie wszystkich w tłumie i eliminowanie wszystkich z wyjątkiem Jacka.

S Renalds
źródło
Problem polega na tym, że OP oczekuje, że Jack będzie jedyną osobą w tłumie. Jeśli tak, to mają szczęście. find jackwyświetli listę, jackjeśli jest to plik o nazwie jack, lub wszystkie nazwy w katalogu, jeśli jest to katalog. To nieporozumienie, jak to finddziała.
Kusalananda
1

Nie zrozumiałem jeszcze problemu, ale mogę dostarczyć więcej informacji.

Podobnie jak w przypadku Kusalanandy, find | greppołączenie jest wyraźnie szybsze w moim systemie, co nie ma większego sensu. Początkowo przyjąłem problem buforowania; zapisywanie na konsoli spowalnia czas do następnego wywołania systemowego w celu odczytania nazwy następnego pliku. Pisanie do potoku jest bardzo szybkie: około 40 Mb / s nawet w przypadku zapisu 32-bajtowego (w moim raczej wolnym systemie; 300 MiB / s dla bloku o wielkości 1 Mb). W związku z tym założyłem, że findmożna szybciej czytać z systemu plików podczas zapisywania do potoku (lub pliku), aby dwie operacje odczytujące ścieżki plików i zapisujące do konsoli mogły działać równolegle (czego findjako proces pojedynczego wątku nie może wykonać samodzielnie.

To findwina

Porównywanie dwóch połączeń

:> time find "$HOME"/ -name '*.txt' >/dev/null

real    0m0.965s
user    0m0.532s
sys     0m0.423s

i

:> time find "$HOME"/ >/dev/null

real    0m0.653s
user    0m0.242s
sys     0m0.405s

pokazuje, że findrobi coś niesamowicie głupiego (cokolwiek to może być). Po prostu okazuje się, że jest dość niekompetentny w wykonywaniu -name '*.txt'.

Może zależeć od stosunku wejścia / wyjścia

Możesz pomyśleć, że find -namewygrywa, jeśli jest bardzo mało do napisania. Ale jest coraz bardziej zawstydzające find. Traci nawet, jeśli nie ma nic do zapisania w stosunku do 200 000 plików (13 mln danych potoku) dla grep:

time find /usr -name lwevhewoivhol

findmoże być jak najszybciej grep, choć

Okazuje się, że findgłupota namenie obejmuje innych testów. Zamiast tego użyj wyrażenia regularnego, a problem zniknął:

:> time find "$HOME"/ -regex '\.txt$' >/dev/null     

real    0m0.679s
user    0m0.264s
sys     0m0.410s

Myślę, że można to uznać za błąd. Czy ktoś chce zgłosić błąd? Moja wersja to find (GNU findutils) 4.6.0

Hauke ​​Laging
źródło
Jak powtarzalne są twoje czasy? Jeśli najpierw wykonałeś -nametest, być może był on wolniejszy z powodu braku buforowania zawartości katalogu. (Podczas testowania -namei -regexznajdę biorą mniej więcej tyle samo czasu, co najmniej raz efekt pamięci podręcznej zostało uwzględnione Oczywiście może to tylko inna wersja. find...)
psmears
@psmears Oczywiście te testy wykonałem kilka razy. Problem buforowania został wspomniany nawet w komentarzach do pytania przed pierwszą odpowiedzią. Moja findwersja to find (GNU findutils) 4.6.0
Hauke ​​Laging
Dlaczego zaskakujące jest to, że dodawanie -name '*.txt'spowalnia find? Musi wykonać dodatkową pracę, testując każdą nazwę pliku.
Barmar
@Barmar Z jednej strony ta dodatkowa praca może być wykonana niezwykle szybko. Z drugiej strony ta dodatkowa praca oszczędza inną pracę. findmusi pisać mniej danych. A pisanie na fajce jest znacznie wolniejszą operacją.
Hauke ​​Laging,
Pisanie na dysk jest bardzo wolne, pisanie na potoku nie jest takie złe, po prostu kopiuje się do bufora jądra. Zauważ, że w pierwszym teście pisanie większej ilości czasu w /dev/nulljakiś sposób zużywa mniej czasu systemowego.
Barmar
0

Uwaga : Zakładam, że masz na myśli find . -name filename(w przeciwnym razie szukasz różnych rzeczy; find filenamefaktycznie szuka ścieżki o nazwie nazwa_pliku , która może nie zawierać prawie żadnych plików, a zatem wychodzi bardzo szybko).


Załóżmy, że masz katalog zawierający pięć tysięcy plików. W większości systemów plików pliki te są faktycznie przechowywane w strukturze drzewa , co pozwala szybko zlokalizować dowolny plik.

Więc kiedy pytasz find, aby zlokalizować plik, którego nazwa wymaga jedynie sprawdzenie, findbędą prosić o tym pliku, a jedynie, że plik, do systemu plików bazowych, które będą czytać bardzo kilka stron z pamięci masowej. Jeśli więc system plików jest wart swojej soli, ta operacja będzie przebiegać znacznie szybciej niż przemierzanie całego drzewa w celu pobrania wszystkich wpisów.

Kiedy prosisz o proste, findjednak dokładnie to robisz, przemierzasz całe drzewo, czytając. Każdy. Pojedynczy. Wejście. W przypadku dużych katalogów może to stanowić problem (jest to dokładnie powód, dla którego kilka programów, które muszą przechowywać wiele plików na dysku, utworzy „drzewa katalogów” o głębokości dwóch lub trzech składników: w ten sposób każdy liść musi zawierać tylko mniej akta).

LSerni
źródło
-2

Załóżmy, że istnieje plik / john / paul / george / ringo / beatles, a szukany plik nazywa się „kamieniami”

find / stones

find porównuje „beatles” do „stones” i upuszcza go, gdy „s” i „b” nie pasują.

find / | grep stones

W tym przypadku find przejdzie „/ john / paul / george / ringo / beatles” do grep i grep będzie musiał przejść przez całą ścieżkę przed ustaleniem, czy pasuje.

grep wykonuje zatem znacznie więcej pracy, dlatego zajmuje więcej czasu

Paranoidalny
źródło
1
Czy próbowałeś?
Hauke ​​Laging
3
Koszt porównań ciągów (wyjątkowo prosty i tani) jest całkowicie niższy niż koszt IO (lub po prostu syscall, jeśli jest buforowany) kosztów wyszukiwania katalogów.
Mat
grep nie jest porównywaniem ciągów, to porównanie wyrażeń regularnych oznacza, że ​​musi przejść przez cały ciąg znaków, dopóki nie znajdzie dopasowania lub nie osiągnie końca. Wyszukiwania katalogów są takie same bez względu na wszystko.
Paranoid
@Paranoid Hm, o jakiej wersji znalezienia mówisz? Najwyraźniej nie przypomina to znaleziska, do którego jestem przyzwyczajony w Debianie.
rura