Mam wyjątkową sytuację, w której mogę przeprowadzić analizę porównawczą rozwiązań zaproponowanych na tej stronie, dlatego piszę tę odpowiedź jako konsolidację proponowanych rozwiązań z uwzględnieniem każdego z nich.
Ustawiać
Mam plik danych tekstowych ASCII o rozmiarze 3,261 gigabajta z jedną parą klucz-wartość na wiersz. Plik zawiera łącznie 3333950320 wierszy i odmawia otwarcia w dowolnym edytorze, którego próbowałem, w tym w moim Vimie. Muszę podzestawić ten plik, aby zbadać niektóre wartości, które odkryłem, zaczynają się tylko wokół rzędu ~ 500 000 000.
Ponieważ plik ma tak wiele wierszy:
- Muszę wyodrębnić tylko podzbiór wierszy, aby zrobić coś użytecznego z danymi.
- Przeczytanie każdego wiersza prowadzącego do wartości, na których mi zależy, zajmie dużo czasu.
- Jeśli rozwiązanie odczyta wiersze, na których mi zależy, i będzie kontynuowało czytanie pozostałej części pliku, straci czas na odczytanie prawie 3 miliardów nieistotnych wierszy i zajmie 6 razy dłużej niż to konieczne.
Mój najlepszy scenariusz to rozwiązanie, które wyodrębnia tylko jeden wiersz z pliku bez odczytywania innych wierszy w pliku, ale nie mogę wymyślić, jak to osiągnę w Bash.
Dla mojego zdrowia psychicznego nie zamierzam czytać pełnych 500 000 000 wierszy, których potrzebowałbym na swój problem. Zamiast tego spróbuję wyodrębnić wiersz 50 000 000 z 3333950320 (co oznacza, że odczyt całego pliku zajmie 60x dłużej niż to konieczne).
Będę używał time
wbudowanego do testowania każdego polecenia.
Linia bazowa
Najpierw zobaczmy, jak head
tail
rozwiązanie:
$ time head -50000000 myfile.ascii | tail -1
pgm_icnt = 0
real 1m15.321s
Linia bazowa dla wiersza 50 milionów to 00: 01: 15.321, gdybym poszedł prosto do wiersza 500 milionów, byłoby to prawdopodobnie około 12,5 minuty.
skaleczenie
Wątpię w to, ale warto spróbować:
$ time cut -f50000000 -d$'\n' myfile.ascii
pgm_icnt = 0
real 5m12.156s
Uruchomienie tego zajęło 00: 05: 12.156, co jest znacznie wolniejsze niż poziom podstawowy! Nie jestem pewien, czy przed zatrzymaniem przeczytał cały plik, czy tylko 50 milionów, ale niezależnie od tego nie wydaje się to realnym rozwiązaniem problemu.
AWK
Uruchomiłem rozwiązanie tylko exit
dlatego, że nie zamierzałem czekać na uruchomienie pełnego pliku:
$ time awk 'NR == 50000000 {print; exit}' myfile.ascii
pgm_icnt = 0
real 1m16.583s
Ten kod działał w 00: 01: 16.583, co jest tylko ~ 1 sekundę wolniejsze, ale wciąż nie stanowi poprawy w stosunku do linii podstawowej. Przy takim tempie, gdyby polecenie zakończenia zostało wykluczone, odczytanie całego pliku zajęłoby prawdopodobnie około 76 minut!
Perl
Uruchomiłem również istniejące rozwiązanie Perla:
$ time perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii
pgm_icnt = 0
real 1m13.146s
Ten kod działał w 00: 01: 13.146, czyli około 2 sekundy szybciej niż poziom podstawowy. Gdybym uruchomił go na pełnych 500 000 000, prawdopodobnie zajęłoby to około 12 minut.
sed
Najlepsza odpowiedź na tablicy, oto mój wynik:
$ time sed "50000000q;d" myfile.ascii
pgm_icnt = 0
real 1m12.705s
Ten kod działał w 00: 01: 12.705, czyli 3 sekundy szybciej niż poziom bazowy i ~ 0,4 sekundy szybciej niż Perl. Gdybym uruchomił go na pełnych 500 000 000 wierszach, prawdopodobnie zajęłoby to około 12 minut.
plik map
Mam bash 3.1 i dlatego nie mogę przetestować rozwiązania mapfile.
Wniosek
Wygląda na to, że w większości trudno jest poprawić to head
tail
rozwiązanie. W najlepszym raziesed
rozwiązanie zapewnia ~ 3% wzrost wydajności.
(procenty obliczone ze wzoru % = (runtime/baseline - 1) * 100
)
Rząd 50 000 000
- 00: 01: 12.705 (-00: 00: 02.616 = -3,47%)
sed
- 00: 01: 13,146 (-00: 00: 02.175 = -2,89%)
perl
- 00: 01: 15.321 (+00: 00: 00.000 = + 0,00%)
head|tail
- 00: 01: 16,583 (+00: 00: 01.262 = + 1,68%)
awk
- 00: 05: 12,156 (+00: 03: 56,835 = + 314,43%)
cut
Wiersz 500 000 000
- 00: 12: 07.050 (-00: 00: 26.160)
sed
- 00: 12: 11.460 (-00: 00: 21.750)
perl
- 00: 12: 33.210 (+00: 00: 00.000)
head|tail
- 00: 12: 45,830 (+00: 00: 12,620)
awk
- 00: 52: 01.560 (+00: 40: 31.650)
cut
Wiersz 3 338,559,320
- 01: 20: 54.599 (-00: 03: 05.327)
sed
- 01: 21: 24.045 (-00: 02: 25.227)
perl
- 01: 23: 49.273 (+00: 00: 00.000)
head|tail
- 01: 25: 13.548 (+00: 02: 35,735)
awk
- 05: 47: 23.026 (+04: 24: 26.246)
cut
awk
ised
jestem pewien, że ktoś może wymyślić również linijkę Perla;)