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

В настоящее время у меня большой текстовый файл в виде двух столбцов. Я пытаюсь напечатать уникальные первые столбцы с их суммой в качестве вывода.

cat src   
a 1
b 1
c 1
d 1
a 1
b 2
c 3
d 4

С базовым awk я могу достичь желаемого результата.

awk -F" " '{a[$1]+=$2;}END{for(i in a)print i" "a[i];}' src
a 2
b 3
c 4
d 5

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

cat src | parallel --pipe awk -F" " '{a[$1]+=$2;}END{for(i in a)print i" "a[i];}'

Любое руководство по этому вопросу будет высоко ценится.

Ответы [ 3 ]

0 голосов
/ 27 июня 2018

Я сильно подозреваю, что это не проблема с awk. Я создал тестовый файл, похожий на ваш, с 100 миллионами строк и размером около 1 гигабайта. В первом поле около 100 тысяч уникальных ключей. На моем не очень быстром ноутбуке ваша команда awk выполняется чуть более минуты.

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

Я бы посоветовал вам запустить свою команду, а затем посмотреть ее с top, чтобы увидеть, что происходит. Ваш awk процесс должен использовать 100 процентов ЦП. Если это не так, top должен показывать проблемы со свопингом или ожиданием ввода / вывода. Удачи.

0 голосов
/ 27 июня 2018

Вы сказали, что входной файл отсортирован, поэтому вы можете значительно улучшить команду awk:

awk -F" " '{if (key!=$1) {print key" "sum; key=$1; sum=0} sum+=$2}
           END {print key" "sum}' inputfile

Эта команда использует постоянный объем памяти вместо линейного количества ключей. Как подозревал Джон , память может быть основным замедлением в вашем случае.

Поскольку ваш пример файла не отсортирован, мы тестируем команду в конвейере после sort

$ sort src | awk ...

a 2
b 3
c 4
d 5

Дополнительную пустую строку в начале можно удалить, добавив еще одну if в команду awk или добавив ... | tail -n +2.

В случае, если ваш входной файл не отсортирован, этот подход будет медленным, даже при использовании LC_ALL=C sort для сортировки быстрее (в моей системе это занимает вдвое меньше времени, чем sort).

Обратите внимание, что это просто улучшение команды awk. Команда datamash, предложенная Романом , также извлекает выгоду из уже отсортированных данных и бьет awk.

0 голосов
/ 27 июня 2018

Я обнаружил, что GNU datamash - самый быстрый инструмент для автономного запуска в таком случае.

Тестовый файл (https://transfer.sh/hL5xL/file) имеет ~ 12M строк и размер 116Mb.

Вот статистика продленного времени:

$ du -sh inputfile 
116M    inputfile

$ wc -l inputfile 
12520872 inputfile

$ time datamash -W -g1 sum 2 <inputfile > /dev/null
real    0m10.990s
user    0m10.388s
sys 0m0.216s

$ time awk '{ a[$1] += $2 }END{ for(i in a) print i, a[i] }' inputfile > /dev/null
real    0m12.361s
user    0m11.664s
sys 0m0.196s

$ time parallel -a inputfile --pipepart --block=11M -q awk '{ a[$1] += $2 }END{ for(i in a) print i, a[i] }' \
| awk '{ a[$1] += $2 }END{ for(i in a) print i, a[i] }' >/dev/null

real    0m8.660s
user    0m12.424s
sys 0m2.760s

Для параллельно комбинация использования подхода parallel + awk.

Для самой последней версии datamash вы можете попробовать:

parallel -a inputfile --pipepart --block=11M datamash -sW -g1 sum 2 | datamash -sW -g1 sum 2

Как вы видите, GNU parallel использовался в качестве последнего подхода, состоящего из комбинации 2 awk команд (одна для агрегации промежуточных результатов и другая для агрегации конечных результатов). Ключевыми опциями GNU parallel здесь являются:

--pipepart
Части трубы физического файла. --pipepart работает аналогично --pipe, но намного быстрее.

--block-size размер
Размер блока в байтах для чтения за один раз.

В моем тестовом примере я указал --block=11M как ~ 10% от основного размера файла. В вашем случае вы можете настроить его на --block=100M.

...