使用并行处理大型文本文件并打印第二列之和的最佳表达

时间:2018-06-27 06:52:52

标签: bash awk gnu-parallel

目前,我有一个两列形式的大文本文件。我正在尝试打印唯一的第一列,并将其总和作为输出。

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-parallel进行相同的操作并击中该位置。

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

任何对此的指导将不胜感激。

3 个答案:

答案 0 :(得分:2)

我发现GNU datamash是在这种情况下运行 独立 的最快工具。

测试文件(https://transfer.sh/hL5xL/file)的行数约为1200万,大小为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 方法,请使用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

答案 1 :(得分:1)

我强烈怀疑awk并不是问题。我已经生成了一个与您相似的测试文件,该文件有1亿行,大小约为1 GB。第一个字段中约有10万个唯一键。在我不太快的笔记本电脑上,您的awk命令仅需一分钟即可运行。

在不了解您的计算机的情况下,我想问题可能是内存不足或I / O速度很慢。在我的系统上,awk需要约512 MB的内存才能存储10万个密钥。如果您有数百万个键,则需要按比例增加内存,并且可能会发现内存不足导致交换的问题。交换对于散列数组和随机键确实非常不好。或者,如果您正在从速度较慢的网络文件系统或旧的USB存储器中读取文件,则可能只是在等待I / O,尽管这种可能性较小。

我建议您运行命令,然后用top观看它,以查看发生了什么。您的awk进程应使用100%的CPU。如果不是,top应该显示交换或I / O等待问题。祝你好运。

答案 2 :(得分:0)

您说输入文件已排序,因此可以大大改善awk命令:

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

此命令使用固定数量的内存,而不是按键数量上的线性内存。与Jon suspected一样,您的情况可能是内存减慢的主要原因。

由于您的示例文件未排序,因此我们在sort之后对管道中的命令进行测试

$ sort src | awk ...

a 2
b 3
c 4
d 5

可以通过在if命令中添加另一个awk或附加... | tail -n +2来删除开头的其他空行。

如果未对输入文件进行排序,即使使用LC_ALL=C sort进行排序速度更快,这种方法也会很慢(与sort相比,它占用了系统时间的一半)。

请注意,这只是对awk命令的改进。 datamash command proposed by Roman还受益于已经排序的数据和节拍awk