Сортировка строк массивного файла по количеству слов в строке (в идеале параллельно) - PullRequest
5 голосов
/ 18 марта 2010

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

17118 17136 17392
17064 17093 17376
17118 17136 17356 17318 12345
17118 17136 17356 17283
17007 17059 17116

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

17118 17136 17356 17318 12345
17118 17136 17356 17283
17118 17136 17392
17064 17093 17376
17007 17059 17116

(Связи - т.е. строки с одинаковым количеством идентификаторов - могут быть отсортированы произвольно.)

Какой самый эффективный способ сортировки этих строк.

Помните о следующих моментах:

  1. Файл, который я хочу отсортировать, может быть больше, чем физическая память машины
  2. Большинство машин, на которых я работаю, имеют несколько процессоров, поэтому было бы идеальным параллельное решение
  3. Идеальным решением будет просто сценарий оболочки (вероятно, с использованием sort ), но я открыт для простых решений на python или perl (или на любом языке, если только это делает задачу простой)
  4. Эта задача в некотором смысле очень проста - я не просто ищу какое-то старое решение, но скорее для простого и прежде всего эффективного решения

ОБНОВЛЕНИЕ 2: лучшее решение

Основываясь на сравнительном анализе предложенных решений (см. Ниже), вот лучшее решение (взятое из Влада, который, в свою очередь, адаптировал его к другим решениям, предложенным здесь). Это довольно умно и даже не использует сортировку

for FILE in infile.* ; do
  awk '{ print >sprintf("tmpfile.%05d.%s", NF, FILE) }' \
    FILE=`basename $FILE` $FILE&
done
wait
ls -1r tmpfile.* | xargs cat >outfile
rm -f tmpfile.*

ОБНОВЛЕНИЕ 1: Результаты сравнительного анализа предлагаемых решений

Для тестирования я взял Cliques, найденный в сети Facebook штата Оклахома. Несортированные файлы, содержащие эти клики, выглядят так же, как и в первом примере, который я показываю выше, содержат 46 462 546 строк, что увеличивает размер файла до 6,4 ГБ. Клики почти равномерно распределены по 8 файлам. Система, на которой я тестирую, содержит 4 физических процессора, каждый с 6 ядрами и кэш-памятью второго уровня объемом 12 МБ, всего 24 ядра. Он также содержит 128 ГБ физической памяти. Поскольку сортируемые строки были разбиты на 8 файлов, большинство из этих решений использовали 8 (или 16) одновременных процессов.

Игнорируя первый наивный подход, я сравнил последние 5 предложений Влада Ромашану (решение, которое я выбрал).

Первое решение было не слишком эффективным:

real    6m35.973s
user    26m49.810s
sys     2m14.080s

Я пытался использовать решения 2, 3 и 4, которые используют файлы FIFO, но каждый из них использовал только один процесс сортировки и, таким образом, занимал много времени (и поэтому я убил их, прежде чем они могли закончить) /

Последнее решение было самым быстрым:

real    1m3.272s
user    1m21.540s
sys     1m22.550s

Обратите внимание, что время использования этого решения составляет 1 м 21 с, что намного лучше, чем у первых решений, 26 минут.

Ответы [ 7 ]

11 голосов
/ 18 марта 2010

A наивный подход может быть просто:

awk '{ print NF " " $0 }' infile| sort -k1,1nr |
 awk '{ $1=""; print $0 }' >outfile

Это сохранит занятость до 3 процессоров. sort не ограничен объемом доступной физической памяти, используйте переключатели -S и -T для настройки объема используемой памяти (-S) перед использованием временных файлов во временном каталоге (-T) на достаточно большом (и в идеале быстром) разделе.

Если вы можете создать несколько входных файлов , поделив работу, ведущую к этапу сортировки, вы сможете:

for FILE in infile.* ; do
  awk '{ print NF " " $0 }' $FILE | sort -k1,1nr >$FILE.tmp&
done
wait
sort -k1,1nr -m infile.*.tmp | awk '{ $1=""; print $0 }' >outfile
rm -f infile.*.tmp

Это будет использовать до N*2 процессоров; более того, последний вид (сортировка слиянием) очень эффективен.

Дальнейшее уточнение для улучшения параллелизма до N*2+1 с использованием FIFO вместо промежуточных файлов, опять же при условии, что возможно несколько входных файлов:

for FILE in infile.* ; do
  mkfifo $FILE.fifo
  awk '{ print NF " " $0 }' $FILE | sort -k1,1nr >$FILE.fifo&
done
sort -k1,1nr -m infile.*.fifo | awk '{ $1=""; print $0 }' >outfile
rm -f infile.*.fifo

Если несколько входных файлов невозможны , вы можете имитировать их (добавляя накладные расходы ввода / вывода, которые, как мы надеемся, будут амортизироваться количеством доступных процессов):

PARALLELISM=5 # I want 5 parallel instances
for N in `seq $PARALLELISM` ; do
  mkfifo infile.$N.fifo
  awk 'NR % '$PARALLELISM'=='$N' { print NF " " $0 }' infile |
    sort -k1,1nr >infile.$N.fifo&
done
sort -k1,1nr -m infile.*.fifo | awk '{ $1=""; print $0 }' >outfile
rm -f infile.*.fifo

Поскольку мы используем модуль по строковому номеру, у нас хорошая локальность, и кеш файловой системы в идеале должен довести стоимость чтения входного файла снова и снова в $PARALLELISM процессах до нуля.

Еще лучше , чтение входного файла только один раз и округление входных строк в несколько sort каналов:

PARALLELISM=5 # I want 5 parallel instances
for N in `seq $PARALLELISM` ; do
  mkfifo infile.$N.fifo1
  mkfifo infile.$N.fifo2
  sort -k1,1nr infile.$N.fifo1 >infile.$N.fifo2&
done
awk '{ print NF " " $0 >("infile." NR % '$PARALLELISM' ".fifo1") }' infile&
sort -k1,1nr -m infile.*.fifo2 | awk '{ $1=""; print $0 }' >outfile
rm -f infile.$N.fifo[12]

Вы должны измерить производительность для различных значений $PARALLELISM, а затем выбрать оптимальное.

EDIT

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

EDIT2

Обновлены все сценарии для предоставленного вами соглашения об именах файлов и исправлена ​​ошибка в последней версии.

Кроме того, при использовании нового соглашения об именах файлов, если ввод / вывод не является узким местом, тогда очень небольшое отклонение от решений dave / niry , вероятно, должно быть еще более эффективным:

   for FILE in infile.* ; do
     awk '{ print >sprintf("tmpfile.%05d.%s", NF, FILE) }' \
       FILE=`basename $FILE` $FILE&
   done
   wait
   ls -1r tmpfile.* | xargs cat >outfile
   rm -f tmpfile.*
5 голосов
/ 18 марта 2010

Интересно, как быстро это будет:

#!/bin/sh
rm -rf /tmp/fb
mkdir /tmp/fb
cd /tmp/fb
awk '{ print $0 > NF }'
ls | sort -nr | xargs cat

Однако не использует много ядер.

1 голос
/ 16 апреля 2014

Для справки, мне нужно добавить, что начиная с версии 8.6 (2010), GNU coreutils (включая сортировку) поддерживает многопоточную сортировку. Я думаю, что по умолчанию (начиная с v8.6) будет использоваться количество ядер в качестве количества потоков, но вы можете указать другое число с помощью

sort <file> --parallel=<N>

1 голос
/ 18 марта 2010

Так как вам не нужно сортировать, просто скопируйте в сегменты, вы можете разбить файлы по количеству токенов, это будет самым быстрым:

perl -ne 'split/\s+/;$t=$#_+1;open $f[$t], sprintf(">%09d",$t) if $f[$t] eq "";$f=$f[$t];print $f $_;'

cat `ls -1r 0*`

кстати, диск будет узким местом, # количество ядер и использование не будут иметь большого значения.

0 голосов
/ 18 марта 2010

Не уверен, что правильно понял вопрос, но я думаю, что подобный быстрой сортировке подход может помочь:

10 split the file into N subfiles, one for each core/cpu
20 sort each partial file using the solutions suggested in some of the answers here
30 once every file is split and sorted, get the first line from each file and put it into a temporary file
40 get the second line from each file, put them in a second temporary file
50 repeat until you have number of temp files == number of cores
60 GOTO 20

В зависимости от количества проходов, вам следует подойти к идеально отсортированному файлу.

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

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

ps2: Я также предполагаю, что вы заинтересованы не в идеально отсортированном файле, а в статистической значимости данных (т.е. как long в среднем длинные строки и т. Д.).

0 голосов
/ 18 марта 2010

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

При первом проходе читайте построчно, записывая три вещи: номер строки, смещение файла и количество слов. Это может быть парализовано без особых затруднений (для заданий, которые начинаются со «случайных» строк в файле, просто добавьте соответствующее начальное число после слов).

Теперь сортируйте список трех записанных вещей по количеству слов в строке. Затем выполните итерацию списка в поисках соответствующего начального смещения.

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

0 голосов
/ 18 марта 2010
awk '{print length,$0}' test.txt | sort -nr | cut -d" " -f2-

Не уверен, насколько хорошо это будет работать, хотя сортировка может обойти ограничения памяти, AFAIK.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...