Подсчет строк в больших файлах


Я обычно работаю с текстовыми файлами размером ~20 Гб, и я очень часто считаю количество строк в данном файле.

то, как я делаю это сейчас, это просто cat fname | wc -l, а это занимает очень много времени. Есть ли решение, которое было бы намного быстрее?

Я работаю в высокопроизводительном кластере с установленным Hadoop. Мне было интересно, может ли помочь подход с уменьшением карты.

Я хотел бы, чтобы решение было так же просто, как одна строка запуска, как wc -l решение, но не конечно, насколько это возможно.

какие идеи?

12 59

12 ответов:

попробуй: sed -n '$=' filename

также кошка не нужна:wc -l filename достаточно в вашем нынешнем виде.

ваш ограничивающий фактор скорости-это скорость ввода/вывода вашего устройства хранения, поэтому изменение между простыми программами подсчета новых строк / шаблонов не поможет, потому что разница в скорости выполнения между этими программами, скорее всего, будет подавлена более медленным диском/хранилищем/тем, что у вас есть.

но если у вас есть один и тот же файл, скопированный между дисками/устройствами, или файл распределен между этими дисками, вы, безусловно, можете выполнять операцию параллельно. Я не знаю конкретно о это Hadoop, но предполагая, что вы можете прочитать 10 Гб файла из 4 разных мест, вы можете запустить 4 разных процесса подсчета строк, каждый из которых находится в одной части файла, и суммировать их результаты:

$ 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 &

обратите внимание на & в каждой командной строки, так что все будет работать параллельно; dd работы как cat здесь, но позвольте нам указать, сколько байтов читать (count * bs байт) и сколько пропустить в начале ввода (skip * bs байт). Он работает в блоках, следовательно, необходимо укажите bs как размер блока. В этом примере я разделил файл 10Gb на 4 равных куска 4Kb * 655360 = 2684354560 байт = 2,5 ГБ, по одному для каждого задания, вы можете настроить скрипт, чтобы сделать это для вас на основе размера файла и количества параллельных заданий, которые вы будете запускать. Вам также нужно суммировать результат выполнения, чего я не сделал из-за отсутствия способности сценария оболочки.

если ваша файловая система достаточно умна, чтобы разделить большой файл между многими устройствами, например RAID или распределенная файловая система или что-то еще, и автоматически распараллеливать запросы ввода-вывода, которые могут быть парализованы, вы можете сделать такое разделение, запустив много параллельных заданий, но используя один и тот же путь к файлу, и у вас все еще может быть некоторое увеличение скорости.

изменить: Другая идея, которая пришла мне в голову, заключается в том, что если строки внутри файла имеют одинаковый размер, вы можете получить точное количество строк, разделив размер файла на размер строки, как в байтах. Вы можете сделать это почти мгновенно в одно задание. Если у вас есть средний размер и вы не заботитесь точно о количестве строк, но хотите получить оценку, вы можете сделать эту же операцию и получить удовлетворительный результат намного быстрее, чем точная операция.

на многоядерном сервере, используйте GNU parallel для параллельного подсчета строк файла. После того, как каждый счетчик строк файлов печатается, bc суммирует все подсчеты строк.

find . -name '*.txt' | parallel 'wc -l {}' 2>/dev/null | paste -sd+ - | bc

чтобы сэкономить место, вы даже можете сохранить все файлы в сжатом виде. Следующая строка распаковывает каждый файл и подсчитывает его строки параллельно, а затем суммирует все подсчеты.

find . -name '*.xz' | parallel 'xzcat {} | wc -l' 2>/dev/null | paste -sd+ - | bc

Если ваши данные находятся на HDFS, возможно, самый быстрый подход заключается в использовании потоковой передачи hadoop. Apache Pig'S COUNT UDF, работает на сумке, и поэтому использует один редуктор для вычисления количества строк. Вместо этого вы можете вручную установить количество редукторов в простом потоковом скрипте hadoop следующим образом:

$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"

обратите внимание, что я вручную задать количество редукторов до 100, но вы можете настроить этот параметр. Как только работа map-reduce выполнена, результат от каждого редуктора сохраняется в отдельный файл. Конечное количество строк-это сумма чисел, возвращаемых всеми редукторами. вы можете получить окончательное количество строк следующим образом:

$HADOOP_HOME/bin/hadoop fs -cat <output_path>/* | paste -sd+ | bc

согласно моему тесту, я могу проверить, что Spark-Shell (на основе Scala) намного быстрее, чем другие инструменты (GREP, SED, AWK, PERL, WC). Вот результат теста, который я запустил на файл, который имел 23782409 строк

time grep -c $ my_file.txt;

реальные 0m44.96s пользователь 0m41. 59s 0м3 представление sys.09с

time wc -l my_file.txt;

реальные 0m37.57с пользователь 0m33. 48s 0м3 представление sys.97s

time sed -n '$=' my_file.txt;

реальные 0m38.22С пользователь 0m28. 05s Сыс 0m10.14С

time perl -ne 'END { $_=$.;if(!/^[0-9]+$/){$_=0;};print "$_" }' my_file.txt;

real 0m23.38С пользователь 0m20. 19s 0м3 представление sys.11С

time awk 'END { print NR }' my_file.txt;

реальные 0m19.90-х пользователь 0m16. 76s 0м3 представление sys.12сек

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.Джода.время.Секунды = PT15S

Hadoop по существу предоставляет механизм для выполнения чего-то похожего на то, что предлагает @Ivella.

HDFS Hadoop (распределенная файловая система) собирается взять ваш файл 20GB и сохранить его в кластере в блоках фиксированного размера. Допустим, вы настроили размер блока 128MB, файл будет разделен на блоки 20x8x128MB.

затем вы запускаете программу сокращения карты по этим данным, по существу подсчитывая линии для каждого блока (на этапе карты) а затем сокращение этих подсчетов блочных строк до окончательного подсчета строк для всего файла.

Что касается производительности, в целом, чем больше ваш кластер, тем лучше производительность (больше wc работает параллельно, на более независимых дисках), но есть некоторые накладные расходы в оркестровке заданий, что означает, что запуск задания на меньших файлах фактически не даст более быстрой пропускной способности, чем запуск локального wc

Я знаю, что вопрос уже несколько лет, но расширяется Ivella это, это bash скрипт смета количество строк большого файла в течение нескольких секунд или меньше, измеряя размер одной строки и экстраполируя из нее:

#!/bin/bash
head -2  | tail -1 > _oneline
filesize=$(du -b  | cut -f -1)
linesize=$(du -b _oneline | cut -f -1)
rm _oneline
echo $(expr $filesize / $linesize)

Если вы назовете этот скрипт lines.sh можно назвать lines.sh bigfile.txt чтобы получить расчетное количество строк. В моем случае (около 6 ГБ, экспорт из базы данных), отклонение от истинного число строк только 3%, но бежал примерно в 1000 раз быстрее. Кстати, я использовал вторую, а не первую строку в качестве основы, потому что первая строка имела имена столбцов, а фактические данные начинались во второй строке.

Я не уверен, что Python быстрее:

[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

если ваше узкое место-это диск, важно, как Вы читаете с него. dd if=filename bs=128M | wc -l Это много быстрее wc -l filename или cat filename | wc -l для моей машины, которая имеет жесткий диск и быстрый процессор и оперативную память. Вы можете поиграть с размером блока и посмотреть, что dd отчеты как пропускная способность. Я провернул его до 1GiB.

Примечание: есть некоторые дебаты о том,cat или dd - это быстрее. Все, что я утверждаю, что dd может быть быстрее, в зависимости от системы, и что это для меня. Попробовать его для себя.

если ваш компьютер имеет python, вы можете попробовать это из оболочки:

python -c "print len(open('test.txt').read().split('\n'))"

использует python -c чтобы передать команду, которая в основном читает файл и разбивается на "новую строку", чтобы получить количество новых строк или общую длину файла.

@у тебя!--10-->:

bash-3.2$ sed -n '$=' test.txt
519

используя вышеуказанные:

bash-3.2$ python -c "print len(open('test.txt').read().split('\n'))"
519

find-type f-name " filepattern_2015_07_*.формат txt" -exec для ЛС -1 {} \; | кошки | у awk '//{ печати $0 , система("кошка" $0 "|" "сан. узел-л")}'

выход:

допустим:

  • ваша файловая система распределена
  • ваша файловая система может легко заполнить сетевое подключение к одному узлу
  • вы получаете доступ к файлам, как обычные файлы

тогда вы действительно хотите нарезать файлы на части, считать части параллельно на нескольких узлах и суммировать результаты оттуда (это в основном идея @Chris White).

вот как вы это делаете с GNU Parallel (версия > 20161222). Вам нужно перечислить узлы в ~/.parallel/my_cluster_hosts и вы должны иметь ssh доступ ко всем из них:

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 "" -vv --dryrun wc "" |
        # 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" }'
}

как использовать:

parwc -l myfile
parwc -w myfile
parwc -c myfile