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?


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.


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


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.


+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

+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


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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.