Które szybciej usunąć pierwszą linię w pliku… sed lub tail?

14

W tej odpowiedzi ( Jak mogę usunąć pierwszy wiersz pliku za pomocą sed? ) Istnieją dwa sposoby usunięcia pierwszego rekordu w pliku:

sed '1d' $file >> headerless.txt

** ---------------- LUB ---------------- **

tail -n +2 $file >> headerless.txt

Osobiście uważam, że ta tailopcja jest kosmetycznie przyjemniejsza i bardziej czytelna, ale prawdopodobnie dlatego, że mam wyzwanie.

Która metoda jest najszybsza?

WinEunuuchs2Unix
źródło
5
Nie jest to odpowiedź, ale możliwym rozwiązaniem jest to, że sedjest bardziej przenośna: „+2” dla taildziała dobrze na Ubuntu, który używa GNU tail, ale nie działa na BSD tail.
John N,
@JohnN dzięki za podzielenie się tailbrakiem zgodności między platformami.
WinEunuuchs2Unix,
3
@John N „+2” dla ogona działa dobrze w maju Mac z Sierra, który twierdzi, że używa polecenia ogona BSD
Nick Sillito,
Urgh, masz całkowitą rację - właśnie uruchomiłem go ponownie i tym razem sprawdziłem dane wejściowe. Co powinienem był zrobić po raz pierwszy. To także POSIX. / znika, zawstydzony.
John N,
2
@JohnN Nie jesteś całkowicie w błędzie. W przeszłości system UNIX nie zapewniał tej -nopcji i korzystał ze składni tail +2 $file. Zobacz freebsd.org/cgi/… Możliwe, że myślałeś o tym, a nie o jednym ze współczesnych BSD.
hvd

Odpowiedzi:

28

Wydajność sedvs., tailaby usunąć pierwszą linię pliku

TL; DR

  • sed jest bardzo wydajny i wszechstronny, ale to powoduje, że działa wolno, szczególnie w przypadku dużych plików z wieloma liniami.

  • tail robi tylko jedną prostą rzecz, ale ta robi dobrze i szybko, nawet dla większych plików z wieloma liniami.

Dla małych i średnich plików sedi taildziałają podobnie szybko (lub wolno, w zależności od oczekiwań). Jednak w przypadku większych plików wejściowych (wiele MB) różnica wydajności znacznie rośnie (rząd wielkości dla plików w zakresie setek MB), przy tailwyraźnie lepszych wynikach sed.

Eksperyment

Ogólne przygotowania:

Nasze polecenia do analizy to:

sed '1d' testfile > /dev/null
tail -n +2 testfile > /dev/null

Zauważ, że przesyłam dane wyjściowe za /dev/nullkażdym razem, aby wyeliminować dane wyjściowe terminala lub zapisy plików jako wąskie gardło wydajności.

Skonfigurujmy dysk RAM, aby wyeliminować We / Wy dysku jako potencjalne wąskie gardło. Ja osobiście mam tmpfszamontowany w, /tmpwięc po prostu umieściłem go testfilew tym eksperymencie.

Następnie raz tworzę losowy plik testowy zawierający określoną liczbę linii $numoflineso losowej długości linii i losowych danych za pomocą tego polecenia (zauważ, że zdecydowanie nie jest optymalny, staje się naprawdę wolny dla około> 2M linii, ale kogo to obchodzi, to nie jest rzecz, którą analizujemy):

cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n "$numoflines" > testfile

Och, przy okazji. mój testowy laptop działa pod kontrolą Ubuntu 16.04, 64-bit na procesorze Intel i5-6200U. Dla porównania.

Czas dużych plików:

Konfigurowanie ogromnej testfile:

Uruchomienie powyższej komendy z numoflines=10000000wygenerowanym losowym plikiem zawierającym 10 mln linii, zajmującym nieco ponad 600 MB - jest dość duże, ale zacznijmy od niego, ponieważ możemy:

$ wc -l testfile 
10000000 testfile

$ du -h testfile 
611M    testfile

$ head -n 3 testfile 
qOWrzWppWJxx0e59o2uuvkrfjQbzos8Z0RWcCQPMGFPueRKqoy1mpgjHcSgtsRXLrZ8S4CU8w6O6pxkKa3JbJD7QNyiHb4o95TSKkdTBYs8uUOCRKPu6BbvG
NklpTCRzUgZK
O/lcQwmJXl1CGr5vQAbpM7TRNkx6XusYrO

Wykonaj bieg na czas z naszym ogromnym testfile:

Teraz zróbmy tylko jeden czas z obu komend, aby oszacować, z jaką wielkością pracujemy.

$ time sed '1d' testfile > /dev/null
real    0m2.104s
user    0m1.944s
sys     0m0.156s

$ time tail -n +2 testfile > /dev/null
real    0m0.181s
user    0m0.044s
sys     0m0.132s

Widzimy już naprawdę wyraźny wynik dla dużych plików, tailjest o wiele szybszy niż sed. Ale dla zabawy i dla pewności, że nie ma przypadkowych efektów ubocznych, które mogą mieć duże znaczenie, zróbmy to 100 razy:

$ time for i in {1..100}; do sed '1d' testfile > /dev/null; done
real    3m36.756s
user    3m19.756s
sys     0m15.792s

$ time for i in {1..100}; do tail -n +2 testfile > /dev/null; done
real    0m14.573s
user    0m1.876s
sys     0m12.420s

Wniosek pozostaje ten sam, sedjest nieefektywny, aby usunąć pierwszą linię dużego pliku, tailnależy tam użyć.

I tak, wiem, że konstrukcje pętli Basha są powolne, ale robimy tutaj tylko kilka iteracji, a czas potrzebny na zwykłą pętlę nie jest znaczący w porównaniu z sed/ tailruntimes.

Czas małe pliki:

Konfigurowanie małego testfile:

Teraz dla kompletności, spójrzmy na bardziej powszechny przypadek, w którym masz mały plik wejściowy w zakresie kB. Utwórzmy losowy plik wejściowy numoflines=100, wyglądający tak:

$ wc -l testfile 
100 testfile

$ du -h testfile 
8,0K    testfile

$ head -n 3 testfile 
tYMWxhi7GqV0DjWd
pemd0y3NgfBK4G4ho/
aItY/8crld2tZvsU5ly

Wykonaj bieg na czas za pomocą naszego małego testfile:

Ponieważ możemy oczekiwać, że czasy tak małych plików mieszczą się w zakresie kilku milisekund od doświadczenia, zróbmy od razu 1000 iteracji:

$ time for i in {1..1000}; do sed '1d' testfile > /dev/null; done
real    0m7.811s
user    0m0.412s
sys     0m7.020s

$ time for i in {1..1000}; do tail -n +2 testfile > /dev/null; done
real    0m7.485s
user    0m0.292s
sys     0m6.020s

Jak widać, czasy są dość podobne, nie ma wiele do interpretacji ani zastanowienia. W przypadku małych plików oba narzędzia są równie dobrze dostosowane.

Bajt Dowódca
źródło
+1 za odpowiedź dziękuję. Zredagowałem oryginalne pytanie (przepraszam) na podstawie komentarza Serga, który również awkmoże to zrobić. Moje oryginalne pytanie opierało się na linku, który znalazłem w pierwszej kolejności. Po całej ciężkiej pracy proszę doradzić, czy powinienem usunąć się awkjako kandydat na rozwiązanie i skupić się na pierwotnym zakresie projektu tylko sedi tail.
WinEunuuchs2Unix,
Co to za system? Na moim Macu (czyli narzędziach BSD) testowanie na / usr / share / dict / words daje mi 0,09 s dla sed i 0,19 s dla ogona (i awk 'NR > 1', co ciekawe).
Kevin
5

Oto kolejna alternatywa, wykorzystująca tylko wbudowane bash i cat:

{ read ; cat > headerless.txt; } < $file

$filezostaje przekierowany do { }grupy poleceń. Po readprostu czyta i odrzuca pierwszą linię. Reszta strumienia jest następnie przesyłana potokowo, do catktórej zapisuje go do pliku docelowego.

Na moim Ubuntu 16.04 wydajność tego i tailrozwiązania jest bardzo podobna. Utworzyłem obszerny plik testowy z seq:

$ seq 100000000 > 100M.txt
$ ls -l 100M.txt 
-rw-rw-r-- 1 ubuntu ubuntu 888888898 Dec 20 17:04 100M.txt
$

tail rozwiązanie:

$ time tail -n +2 100M.txt > headerless.txt

real    0m1.469s
user    0m0.052s
sys 0m0.784s
$ 

cat/ rozwiązanie klamrowe:

$ time { read ; cat > headerless.txt; } < 100M.txt 

real    0m1.877s
user    0m0.000s
sys 0m0.736s
$ 

Mam jednak teraz tylko pod ręką maszynę Wirtualną Ubuntu i widziałem znaczne różnice w czasie obu tych programów, choć wszystkie są na tym samym boisku.

Cyfrowa trauma
źródło
1
+1 za odpowiedź dziękuję. To bardzo interesujące rozwiązanie i uwielbiam nawiasy klamrowe i czytanie od prawej do lewej za pomocą hierarchii bash. (nie jestem pewien, czy sformułowałem to poprawnie). Czy można zaktualizować odpowiedź o rozmiar pliku wejściowego i wyniki testu porównawczego taktowania, jeśli jest to wystarczająco łatwe?
WinEunuuchs2Unix
@ Dodano czasy WinEunuuchs2Unix, choć nie są one zbyt niezawodne, ponieważ dotyczy to maszyny wirtualnej. Nie mam teraz praktycznej instalacji Ubuntu bez systemu operacyjnego.
Cyfrowy uraz
I tak nie sądzę, że VM kontra Bare Metal ma znaczenie, kiedy porównujesz VM do VM. Dzięki za dowód czasu. Prawdopodobnie bym poszedł, tailale nadal uważam, że readopcja jest bardzo fajna.
WinEunuuchs2Unix,
4

Próbując w moim systemie i poprzedzając każdą komendę time, otrzymałem następujące wyniki:

sed:

real    0m0.129s
user    0m0.012s
sys     0m0.000s

i ogon:

real    0m0.003s
user    0m0.000s
sys     0m0.000s

co sugeruje, że w moim systemie przynajmniej AMD FX 8250 z systemem Ubuntu 16.04, tail jest znacznie szybszy. Plik testowy miał 10 000 linii o rozmiarze 540k. Plik został odczytany z dysku twardego.

Nick Sillito
źródło
+1 za odpowiedź dziękuję. W osobnym teście w AU Chatroom jeden użytkownik pokazał, że ogon jest 10 razy szybszy (2,31 sekundy) niż sed (21,86 sekundy) przy użyciu RAMDisk z plikiem 61 MB. Zredagowałem twoją odpowiedź, aby zastosować bloki kodu, ale możesz też chcieć ją edytować przy używanym rozmiarze pliku.
WinEunuuchs2Unix,
@Serg Absolutnie uczciwe, że jest to tylko anegdotyczna odpowiedź, i potencjalnie można uzyskać różne wyniki przy różnych konfiguracjach sprzętu, różnych plikach testowych itp.
Nick Sillito
2
Plik, który nie znajduje się w pamięci podręcznej, podczas używania sedmoże odgrywać rolę w tym wyniku, w takiej kolejności, w jakiej zostałeś przetestowany.
Minix
jaki system? Jak skomentowałem inny post tutaj, na moim komputerze Mac sedbyło około dwa razy szybsze.
Kevin
1

Nie ma obiektywnego sposobu, aby powiedzieć, co jest lepsze, ponieważ sedi tailnie są jedynymi rzeczami, które działają w systemie podczas wykonywania programu. Wiele czynników, takich jak dyskowe operacje we / wy, sieciowe operacje we / wy, przerwania procesora dla procesów o wyższym priorytecie - wszystkie te czynniki wpływają na szybkość działania programu.

Oba są napisane w C, więc nie jest to kwestia językowa, ale bardziej środowiskowa. Na przykład mam dysk SSD i w moim systemie zajmie to trochę czasu w mikrosekundach, ale w przypadku tego samego pliku na dysku twardym zajmie to więcej czasu, ponieważ dyski twarde są znacznie wolniejsze. Sprzęt również odgrywa w tym rolę.

Podczas rozważania wyboru polecenia należy pamiętać o kilku rzeczach:

  • Jaki jest Twój cel ? sedto edytor strumieniowy do przekształcania tekstu. tailsłuży do wyprowadzania określonych wierszy tekstu. Jeśli chcesz poradzić sobie z liniami i tylko je wydrukować, użyj tail. Jeśli chcesz edytować tekst, użyj sed.
  • tailma znacznie prostszą składnię niż sed, więc używaj tego, co możesz przeczytać sam i tego, co inni mogą przeczytać.

Innym ważnym czynnikiem jest ilość przetwarzanych danych. Małe pliki nie dają żadnej różnicy w wydajności. Obraz staje się interesujący, gdy masz do czynienia z dużymi plikami. Dzięki plikowi BIGFILE.txt o pojemności 2 GB możemy zauważyć, że sedma o wiele więcej wywołań systemowych taili działa znacznie wolniej.

bash-4.3$ du -sh BIGFILE.txt 
2.0G    BIGFILE.txt
bash-4.3$ strace -c  sed '1d' ./BIGFILE.txt  > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 59.38    0.079781           0    517051           read
 40.62    0.054570           0    517042           write
  0.00    0.000000           0        10         1 open
  0.00    0.000000           0        11           close
  0.00    0.000000           0        10           fstat
  0.00    0.000000           0        19           mmap
  0.00    0.000000           0        12           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         2           rt_sigaction
  0.00    0.000000           0         1           rt_sigprocmask
  0.00    0.000000           0         1         1 ioctl
  0.00    0.000000           0         7         7 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0         2         2 statfs
  0.00    0.000000           0         1           arch_prctl
  0.00    0.000000           0         1           set_tid_address
  0.00    0.000000           0         1           set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00    0.134351               1034177        11 total
bash-4.3$ strace -c  tail  -n +2 ./BIGFILE.txt  > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 62.30    0.148821           0    517042           write
 37.70    0.090044           0    258525           read
  0.00    0.000000           0         9         3 open
  0.00    0.000000           0         8           close
  0.00    0.000000           0         7           fstat
  0.00    0.000000           0        10           mmap
  0.00    0.000000           0         4           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         1         1 ioctl
  0.00    0.000000           0         3         3 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00    0.238865                775615         7 total
Sergiy Kolodyazhnyy
źródło
+1 za odpowiedź dziękuję. Ale nie jestem pewien, czy ten komentarz pomaga mi zdecydować, którego polecenia powinienem użyć ....
WinEunuuchs2Unix,
@ WinEunuuchs2Unix Cóż, zapytałeś, które polecenie jest lepsze, więc odpowiadam dokładnie na to pytanie. Które polecenie wybrać, zależy od ciebie. Jeśli potrafisz czytać taillepiej niż sed- użyj tego. Osobiście skorzystałbym, pythona awkraczej seddlatego, że może się skomplikować. Poza tym, jeśli martwisz się wydajnością, spójrzmy prawdzie w oczy - tutaj widzisz wyniki w mikrosekundach. Nie poczujesz różnicy, chyba że jest to cholernie ogromny plik w zasięgu gigabajtów, który próbujesz przeczytać
Sergiy Kolodyazhnyy,
Och, też doceniłbym awkodpowiedź:) ... Moje pytanie było oparte na innym pytaniu i odpowiedziach AU (w linku) i tam nigdy nie wspomniano awk. Zgadzam się, że różnica czasu jest nominalna dla małych plików. Próbowałem tylko rozwinąć dobre nawyki.
WinEunuuchs2Unix,
1
@ WinEunuuchs2Unix pewno, to jest tutaj: awk 'NR!=1' input_file.txt . Daje mi ten sam wynik, około 150 milisekund, tę samą liczbę dla obu taili sed. Ale z drugiej strony, używam SSD, więc powiedziałbym, że liczy się dysk twardy i procesor, a nie polecenie.
Sergiy Kolodyazhnyy,
1
@Serg nawet przy zaledwie 60 MB pliku zawierającym 1M linii, 1000 uruchomień sedzajmuje znacznie ponad 3 minuty, podczas gdy tailpotrzebuje tylko około 20 sekund. To naprawdę nie jest tak duże, ale zdecydowanie nie w zakresie GB.
Bajt Dowódca
1

Najlepsza odpowiedź nie uwzględniała działania dysku > /dev/null

jeśli masz duży plik i nie chcesz tworzyć tymczasowego duplikatu na dysku, spróbuj vim -c

$ cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n 10000000 > testfile
$ time sed -i '1d' testfile

real    0m59.053s
user    0m9.625s
sys     0m48.952s

$ cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n 10000000 > testfile
$ time vim -e -s testfile -c ':1d' -c ':wq'

real    0m8.259s
user    0m3.640s
sys     0m3.093s

Edycja: jeśli plik jest większy niż dostępna pamięć vim -cnie działa, wygląda na to, że nie jest wystarczająco inteligentny, aby wykonać przyrostowe ładowanie pliku

StevenWernerCS
źródło
0

Inne odpowiedzi pokazują dobrze, co jest lepsze, aby utworzyć nowy plik bez pierwszej linii. Jeśli chcesz edytować plik w przeciwieństwie do tworzenia nowego pliku, założę się, edże byłby szybszy, ponieważ w ogóle nie powinien tworzyć nowego pliku. Ale musisz poszukać, jak usunąć linię, edponieważ użyłem jej tylko raz.

akostadinov
źródło