Mam katalog zawierający ponad 400 GiB danych. Chciałem sprawdzić, czy wszystkie pliki można odczytać bez błędów, więc pomyślałem o tar
tym w prosty sposób /dev/null
. Zamiast tego widzę następujące zachowanie:
$ time tar cf /dev/null .
real 0m4.387s
user 0m3.462s
sys 0m0.185s
$ time tar cf - . > /dev/null
real 0m3.130s
user 0m3.091s
sys 0m0.035s
$ time tar cf - . | cat > /dev/null
^C
real 10m32.985s
user 0m1.942s
sys 0m33.764s
Trzecie polecenie powyżej zostało siłą zatrzymane przez Ctrl+ Cpo długim biegu. Ponadto, podczas gdy pierwsze dwa polecenia działały, wskaźnik aktywności zawierającego urządzenie pamięci masowej .
był prawie zawsze bezczynny. Po trzecim poleceniu wskaźnik świeci się stale, co oznacza ekstremalne zajęcie.
Wygląda więc na to, że gdy tar
jest w stanie dowiedzieć się, że jego plik wyjściowy jest /dev/null
, tzn. Kiedy /dev/null
jest bezpośrednio otwierany, aby mieć uchwyt pliku, w którym tar
zapisuje, ciało wydaje się pominięte. (Dodanie v
opcji tar
powoduje wydrukowanie wszystkich plików w katalogu na tar
czerwono).
Zastanawiam się więc, dlaczego tak jest? Czy to jakaś optymalizacja? Jeśli tak, to dlaczego miałby tar
chcieć dokonać tak wątpliwej optymalizacji tak wyjątkowego przypadku?
Używam GNU tar 1.26 z glibc 2.27 na Linuksie 4.14.105 amd64.
find . -type f -exec shasum -a256 -b '{}' +
. Nie tylko faktycznie odczytuje i sumuje wszystkie dane, ale jeśli przechowujesz dane wyjściowe, możesz je ponownie uruchomić później, aby sprawdzić, czy zawartość plików się nie zmieniła.pv
:tar -cf - | pv >/dev/null
. To omija problem i daje informacje o postępie (różnepv
opcje)gtar -cf /dev/zero ...
aby uzyskać to, co lubisz.Odpowiedzi:
Jest to udokumentowana optymalizacja :
źródło
info tar
zamiast tego spróbować ...info
lub jako HTML w przeglądarce.Może się to zdarzyć w przypadku różnych programów, na przykład miałem takie zachowanie raz, gdy tylko używałem
cp file /dev/null
; zamiast uzyskać szacunkową prędkość odczytu dysku, polecenie powróciło po kilku milisekundach.O ile pamiętam, było to w systemie Solaris lub AIX, ale zasada ta dotyczy wszystkich rodzajów systemów unix-y.
W dawnych czasach, gdy program kopiował gdzieś plik, występował na przemian między
read
wywołaniami, które pobierają dane z dysku (lub dowolnego innego deskryptora pliku) do pamięci (z gwarancją, że wszystko jest tam, gdyread
wraca) iwrite
wywołań (które zajmują kawałek pamięci i wysyłają zawartość do miejsca docelowego).Istnieją jednak co najmniej dwa nowsze sposoby osiągnięcia tego samego:
Linux ma wywołania systemowe
copy_file_range
(w ogóle nieprzenośne dla innych uniksów) isendfile
(w pewnym stopniu przenośne; pierwotnie zamierzał wysłać plik do sieci, ale teraz może używać dowolnego miejsca docelowego). Mają na celu optymalizację transferów; jeśli program używa jednego z nich, łatwo można sobie wyobrazić, że jądro rozpoznaje cel/dev/null
i zamienia wywołanie systemowe w brak działaniaProgramy mogą
mmap
zamiastread
tego pobierać zawartość pliku , co w zasadzie oznacza „upewnij się, że dane tam są, gdy próbuję uzyskać dostęp do tej części pamięci” zamiast „upewnij się, że dane tam są, gdy wywołanie systemowe wróci”. Program może więc pobraćmmap
plik źródłowy, a następnie wywołaćwrite
tę część zmapowanej pamięci. Ponieważ jednak zapis/dev/null
nie musi uzyskiwać dostępu do zapisanych danych, warunek „upewnij się, że tam jest” nie jest nigdy uruchamiany, co powoduje, że plik też nie jest odczytywany.Nie jestem pewien, czy gnu tar używa któregoś z tych dwóch mechanizmów po wykryciu, że pisze
/dev/null
, ale są one powodem, dla którego dowolny program, używany do sprawdzania prędkości odczytu , powinien być uruchamiany| cat > /dev/null
zamiast> /dev/null
- i dlaczego| cat > /dev/null
powinien należy unikać we wszystkich innych przypadkach.źródło
tar
stronie informacyjnej GNU (patrz inna odpowiedź) jest taka, że ma do tego specjalny tryb, który prawdopodobnie tylko statystyki plików bez ich otwierania. W rzeczywistości sprawdziłem tylkotar cf /dev/null foo*
kilka plików i tak, po prostunewfstatat(..., AT_SYMLINK_NOFOLLOW)
wywołania systemowe, nawet te,open()
które mogłyby zaktualizować atime. Ale +1 za opisanie mechanizmów, w których może się to zdarzyć bez konieczności specjalnego wykrywania.splice(2)
w systemie Linux. W rzeczywistości zastąpieniecat > /dev/null
przezpv -q > /dev/null
(które używasplice()
w Linuksie) prawdopodobnie zmniejszyłoby narzut. Lubdd bs=65536 skip=9999999999 2> /dev/null
, lubwc -c > /dev/null
lubtail -c1 > /dev/null
...