Licz linie w dużych plikach
Zwykle pracuję z plikami tekstowymi o rozmiarze ~20 Gb i bardzo często zliczam liczbę linii w danym pliku.
Sposób, w jaki teraz to robię, jest po prostu cat fname | wc -l
i trwa to bardzo długo. Czy jest jakieś rozwiązanie, które byłoby znacznie szybsze?
Pracuję w klastrze o wysokiej wydajności z zainstalowanym Hadoopem. Zastanawiałem się, czy podejście do redukcji map mogłoby pomóc.
Chciałbym, aby rozwiązanie było tak proste jak jedna linia, jak rozwiązanie wc -l
, ale nie wiem, jak to wykonalne jest.
12 answers
Try: sed -n '$=' filename
Również kot jest niepotrzebny: wc -l filename
Wystarczy w obecny sposób.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-03 20:45:39
Twój limitujący Współczynnik prędkości jest prędkością We/Wy urządzenia pamięci masowej, więc zmiana pomiędzy prostymi znakami nowej linii/programami zliczającymi wzorce nie pomoże, ponieważ różnica prędkości wykonania między tymi programami może być tłumiona przez sposób wolniejszy Dysk/Pamięć / cokolwiek masz.
Ale jeśli masz ten sam plik skopiowany na dyski/urządzenia lub plik jest rozprowadzany między tymi dyskami, możesz z pewnością wykonać operację równolegle. Nie wiem dokładnie o tym Hadoop, ale zakładając, że możesz odczytać plik 10gb z 4 różnych lokalizacji, możesz uruchomić 4 różne procesy liczenia linii, każdy w jednej części pliku i zsumować ich wyniki:
$ dd bs=4k count=655360 if=/path/to/copy/on/disk/1/file | wc -l &
$ dd bs=4k skip=655360 count=655360 if=/path/to/copy/on/disk/2/file | wc -l &
$ dd bs=4k skip=1310720 count=655360 if=/path/to/copy/on/disk/3/file | wc -l &
$ dd bs=4k skip=1966080 if=/path/to/copy/on/disk/4/file | wc -l &
Zwróć uwagę na &
w każdym wierszu poleceń, więc wszystkie będą działać równolegle; dd
działa jak cat
tutaj, ale pozwól nam określić, ile bajtów do odczytania (count * bs
bajtów) i ile bajtów do pominięcia na początku wejścia (skip * bs
bajtów). Działa w blokach, stąd potrzeba określenia bs
jako bloku rozmiar. W tym przykładzie podzieliłem plik 10Gb na 4 równe kawałki 4Kb * 655360 = 2684354560 bajtów = 2.5 GB, po jednym dla każdego zadania, możesz ustawić skrypt, który zrobi to za Ciebie na podstawie rozmiaru pliku i liczby równoległych zadań, które będziesz uruchamiał. Musisz również podsumować wynik egzekucji, czego nie zrobiłem z powodu mojego braku zdolności skryptu powłoki.
Jeśli Twój system plików jest wystarczająco inteligentny, aby podzielić duży plik między wiele urządzeń, takich jak RAID lub rozproszony system plików lub coś, i automatycznie paralelizować żądania We / Wy, które mogą być sparalelizowane, można zrobić taki podział, uruchamiając wiele równoległych zadań, ale używając tej samej ścieżki pliku, i nadal może mieć pewien wzrost prędkości.
Edytuj: Innym pomysłem, który przyszło mi do głowy, jest to, że jeśli linie wewnątrz pliku mają ten sam rozmiar, można uzyskać dokładną liczbę linii, dzieląc Rozmiar Pliku przez rozmiar linii, zarówno w bajtach. Możesz to zrobić niemal natychmiast w jednej pracy. Jeśli masz średni rozmiar nie dbaj dokładnie o liczbę linii, ale chcesz oszacować, możesz wykonać tę samą operację i uzyskać zadowalający wynik znacznie szybciej niż dokładna operacja.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-13 19:40:13
Na serwerze wielordzeniowym użyj GNU parallel do liczenia linii plików równolegle. Po wydrukowaniu każdego z plików liczba wierszy, BC sumuje wszystkie liczby wierszy.
find . -name '*.txt' | parallel 'wc -l {}' 2>/dev/null | paste -sd+ - | bc
Aby zaoszczędzić miejsce, możesz nawet zachować wszystkie pliki skompresowane. Następująca linia rozpakowuje każdy plik i zlicza jego linie równolegle, a następnie sumuje wszystkie zliczenia.
find . -name '*.xz' | parallel 'xzcat {} | wc -l' 2>/dev/null | paste -sd+ - | bc
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-25 17:56:49
Jeśli Twoje dane znajdują się na HDFS, być może najszybszym rozwiązaniem jest użycie strumieniowania hadoop. Apache Pig ' s COUNT UDF, działa na worku i dlatego używa jednego reduktora do obliczania liczby wierszy. Zamiast tego można ręcznie ustawić liczbę reduktorów w prostym skrypcie strumieniowym hadoop w następujący sposób:
$HADOOP_HOME/bin/hadoop jar $HADOOP_HOME/hadoop-streaming.jar -Dmapred.reduce.tasks=100 -input <input_path> -output <output_path> -mapper /bin/cat -reducer "wc -l"
Zauważ, że ręcznie ustawiłem liczbę reduktorów na 100, ale możesz dostroić ten parametr. Po wykonaniu zadania map-reduce wynik z każdego reduktora jest przechowywany w osobnym plik. Ostateczna liczba wierszy jest sumą liczb zwracanych przez wszystkie reduktory. możesz uzyskać końcową liczbę wierszy w następujący sposób:
$HADOOP_HOME/bin/hadoop fs -cat <output_path>/* | paste -sd+ | bc
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-27 21:56:00
Zgodnie z moim testem mogę sprawdzić, czy Spark-Shell (oparty na Scali) jest o wiele szybszy niż inne narzędzia (GREP, SED, AWK, PERL, WC). Oto wynik testu, który uruchomiłem na pliku, który miał 23782409 linii
time grep -c $ my_file.txt;
Real 0m44. 96s
user 0m41. 59s
sys 0m3. 09S
time wc -l my_file.txt;
Real 0m37. 57s
user 0m33. 48s
sys 0m3. 97s
time sed -n '$=' my_file.txt;
Real 0m38.22s
user 0m28. 05s
sys 0m10. 14s
time perl -ne 'END { $_=$.;if(!/^[0-9]+$/){$_=0;};print "$_" }' my_file.txt
;
time awk 'END { print NR }' my_file.txt;
Real 0m19. 90s
user 0m16. 76s
sys 0m3. 12s
spark-shell
import org.joda.time._
val t_start = DateTime.now()
sc.textFile("file://my_file.txt").count()
val t_end = DateTime.now()
new Period(t_start, t_end).toStandardSeconds()
Res1: org.joda.czas.Sekund = PT15S
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-25 14:28:20
Hadoop zasadniczo zapewnia mechanizm do wykonania czegoś podobnego do tego, co sugeruje @Ivella.
HDFS (Distributed file system) firmy Hadoop zabierze Twój plik 20GB i zapisze go w klastrze w blokach o stałym rozmiarze. Powiedzmy, że skonfigurujesz rozmiar bloku na 128MB, plik zostanie podzielony na bloki 20x8x128mb.
Następnie uruchomisz program Map reduce nad tymi danymi, zasadniczo licząc linie dla każdego bloku (w etapie map), a następnie redukcja tych liczeń linii bloków do końcowej liczenia linii dla całego pliku.
Jeśli chodzi o wydajność, ogólnie rzecz biorąc, im większy klaster, tym lepsza wydajność( więcej wc działa równolegle, na bardziej niezależnych dyskach), ale istnieje pewien narzut w orkiestracji zadań, który oznacza, że uruchomienie zadania na mniejszych plikach nie przyniesie szybszej przepustowości niż uruchomienie lokalnego wc
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-04 01:20:39
Wiem, że pytanie ma już kilka lat, ale rozszerzając ostatni pomysł Ivelli , Ten skrypt Bashaszacunki liczba linii dużego pliku w ciągu kilku sekund lub mniej, mierząc rozmiar jednej linii i ekstrapolując z niej:
#!/bin/bash
head -2 $1 | tail -1 > $1_oneline
filesize=$(du -b $1 | cut -f -1)
linesize=$(du -b $1_oneline | cut -f -1)
rm $1_oneline
echo $(expr $filesize / $linesize)
Jeśli nazwiesz ten skrypt lines.sh
, możesz wywołać lines.sh bigfile.txt
, aby uzyskać szacunkową liczbę linii. W moim przypadku (około 6 GB, eksport z bazy danych) odchylenie od rzeczywistej liczby linii było tylko 3%, ale działało około 1000 razy szybciej. Nawiasem mówiąc, użyłem drugiej, a nie pierwszej linii jako podstawy, ponieważ pierwsza linia miała nazwy kolumn, a rzeczywiste dane zaczynały się w drugiej linii.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-27 07:35:34
Nie jestem pewien czy python jest szybszy:
[root@myserver scripts]# time python -c "print len(open('mybigfile.txt').read().split('\n'))"
644306
real 0m0.310s
user 0m0.176s
sys 0m0.132s
[root@myserver scripts]# time cat mybigfile.txt | wc -l
644305
real 0m0.048s
user 0m0.017s
sys 0m0.074s
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-21 12:31:47
Jeśli twoim wąskim gardłem jest dysk, liczy się sposób odczytu z niego. dd if=filename bs=128M | wc -l
jest lot szybszy niż wc -l filename
lub cat filename | wc -l
dla mojego komputera, który ma HDD i szybki procesor i RAM. Możesz pobawić się rozmiarem bloku i zobaczyć, co dd
raportuje jako przepustowość. Podkręciłem go do 1GiB.
Uwaga: Istnieje dyskusja na temat tego, czy cat
lub dd
jest szybszy. Wszystko, co twierdzę, to to, że dd
może być szybsze, w zależności od systemu, i że jest to dla mnie. Spróbuj sam.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-06 01:48:54
Jeśli Twój komputer ma Pythona, możesz spróbować tego z powłoki:
python -c "print len(open('test.txt').read().split('\n'))"
To używa python -c
do przekazania polecenia, które jest w zasadzie odczytaniem pliku i podzieleniem przez "nową linię", aby uzyskać liczbę nowych linii lub całkowitą długość pliku.
bash-3.2$ sed -n '$=' test.txt
519
Używając powyższego:
bash-3.2$ python -c "print len(open('test.txt').read().split('\n'))"
519
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-11 02:16:51
Find-type f-name " filepattern_2015_07_*.txt "- exec ls -1 {}\; / cat / awk ' / / {print $0, system ("cat" $0 " | "wc-l")} '
Wyjście:
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-30 13:19:31
Załóżmy:
- Twój system plików jest dystrybuowany
- Twój system plików może łatwo wypełnić połączenie sieciowe do pojedynczego węzła
- masz dostęp do swoich plików jak normalne pliki
Następnie naprawdę chcesz pociąć pliki na części, liczyć części równolegle na wielu węzłach i podsumować wyniki stamtąd(to jest w zasadzie pomysł @Chris White).
Oto Jak to zrobić z GNU Parallel (wersja > 20161222). Musisz wymienić węzły w ~/.parallel/my_cluster_hosts
i musisz mieć ssh
dostęp do wszystkich z nich:
parwc() {
# Usage:
# parwc -l file
# Give one chunck per host
chunks=$(cat ~/.parallel/my_cluster_hosts|wc -l)
# Build commands that take a chunk each and do 'wc' on that
# ("map")
parallel -j $chunks --block -1 --pipepart -a "$2" -vv --dryrun wc "$1" |
# For each command
# log into a cluster host
# cd to current working dir
# execute the command
parallel -j0 --slf my_cluster_hosts --wd . |
# Sum up the number of lines
# ("reduce")
perl -ne '$sum += $_; END { print $sum,"\n" }'
}
Użyj jako:
parwc -l myfile
parwc -w myfile
parwc -c myfile
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-19 08:19:47