按行数(理想情况下并行)对大量文件行进行排序

时间:2010-03-17 21:50:11

标签: shell unix sorting

我正在研究一种用于分析Facebook社交网络数据的社区检测算法。第一个任务,检测图中的所有派系,可以并行有效地完成,并给我一个这样的输出:

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

这些行中的每一行代表一个独特的团队(节点ID的集合),我想按每行的ID数降序排列这些行。在上面的例子中,输出应该是这样的:

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

(领带---即具有相同数量的ID的行 - 可以任意排序。)

对这些行进行排序的最有效方法是什么。

请记住以下几点:

  1. 我想要排序的文件可能比机器的物理内存大
  2. 我运行的大部分机器都有多个处理器,因此并行解决方案是理想的
  3. 理想的解决方案只是一个shell脚本(可能使用 sort ),但我对python或perl(或任何语言,如只要它使任务变得简单)
  4. 此任务在某种意义上非常简单 - 我不只是寻找任何旧的解决方案,而是寻求简单且最重要的高效解决方案 < / LI>

    更新2:最佳解决方案

    基于所提出的解决方案的基准测试(见下文),这是最佳解决方案(取自Vlad,后者又将其与此处提出的其他解决方案相匹配)。它非常聪明,甚至不使用sort

    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:对提议的解决方案的结果进行基准测试

    对于基准测试,我采用了在俄克拉荷马州Facebook网络中发现的Cliques。包含这些派系的未分类文件看起来就像我上面显示的第一个示例,包含46,362,546行,这使文件大小达到6.4 GB。这些派系几乎均匀地分布在8个文件中。我正在测试它的系统包含4个物理处理器,每个处理器有6个内核和12MB二级高速缓存,总共24个内核。它还包含128 GB的物理内存。因为要排序的行被分成8个文件,所以这些解决方案中的大多数使用了8个(或16个)并发进程。

    忽略了第一个天真的方法,我对Vlad Romascanu的最后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 个答案:

答案 0 :(得分:11)

天真的方法可能只是:

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

这将使3个CPU忙碌。 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个CPU;而且,最后一种排序(merge-sort)效率很高。

通过使用FIFO 而不是中间文件进一步精炼到N*2+1的并行性,再次假设可能有多个输入文件:

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

如果无法输入多个文件,您可以模拟它们(添加I / O开销,希望按可用进程数量分摊):

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的各种值的效果,然后选择最佳值。

修改

如其他帖子所示,您当然可以使用cut代替最终awk(即剥离第一列),以提高效率。 :)

EDIT2

更新了您提供的文件名约定的所有脚本,并修复了上一版本中的错误。

此外,使用新的文件名约定,如果I / O不是瓶颈,那么 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.*

答案 1 :(得分:5)

我想知道这会有多快:

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

虽然没有利用很多核心。

答案 2 :(得分:1)

由于您不需要排序,只需复制到存储桶中,您可以按令牌数量分割文件,这将是最快的:

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*`
顺便说一句,磁盘将成为瓶颈,核心数和使用率并不重要。

答案 3 :(得分:1)

作为参考,我需要补充一点,从版本8.6(2010)开始,GNU coreutils(包括sort)支持多线程排序。默认情况下,我认为,(自v8.6起)它将使用核心数作为线程数,但您可以使用

指定不同的数字

sort <file> --parallel=<N>

答案 4 :(得分:0)

awk '{print length,$0}' test.txt | sort -nr | cut -d" " -f2-

虽然排序可以解决内存限制AFAIK。

,但不确定它的效果如何

答案 5 :(得分:0)

为了创造一些有效的东西,我会做以下的事情,对文件进行两遍解析:

在第一遍中逐行读取,记录三件事:行号,文件偏移量和字数。这可以在没有太多困难的情况下进行并列化(对于从文件中“随机”行开始的作业,只需添加相应的起始编号)。

现在按行数按字数对三个记录事物的列表进行排序。然后迭代列表,寻找相应的起始偏移量。

从性能的角度来看,所有的搜索都可能很慢,但它应该对内存消耗相对较轻,只需每行需要3个整数。

答案 6 :(得分:0)

我不确定我是否正确理解了这个问题,但我认为类似于快速排序的方法可能有所帮助:

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:如果部分文件仍然大于可用内存,则再次拆分它们直到它们适合(取决于您为每个文件使用的排序算法,tho)。但在这种情况下,你需要将传递次数加倍才能得到合理的近似值

ps2:我还假设你对完美排序的文件不感兴趣,但更多的是数据的统计显着性(即 long 平均长线如何等)。