Bash 使用parallel对大型文本文件进行排序并打印第二列总和的最佳表达式

Bash 使用parallel对大型文本文件进行排序并打印第二列总和的最佳表达式,bash,awk,gnu-parallel,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并行运行相同的程序,并在那里进行了尝试 c

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

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];}'
对此,如有任何指导,将不胜感激

在这种情况下,我发现它是运行独立运行的最快工具

测试文件()的行数约为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
如您所见,GNU
parallel
被用作最后一种方法,由2个
awk
命令组合而成(一个用于聚合中间结果,另一个用于聚合最终结果)。 重要的GNU
并行
选项如下:

--pipepart

管道是物理文件的一部分
--pipepart
的工作原理与
--pipe
类似,但速度要快得多

--块大小
大小
一次读取的块大小(字节)


在我的测试用例中,我将
--block=11M
指定为主文件大小的10%。在您的情况下,您可以将其调整为
--block=100M

我强烈怀疑这不是
awk
的问题。我已经生成了一个测试文件,与您的类似,有1亿行,大小约为1GB。第一个字段中大约有10万个唯一密钥。在我速度不太快的笔记本电脑上,您的
awk
命令只需一分钟就可以运行

在对您的计算机一无所知的情况下,我猜问题要么是内存不足,要么是I/O速度太慢。在我的系统上,
awk
需要大约512 MB的内存才能存储10万个密钥。如果您有数百万个密钥,您将需要相应的更多内存,并且可能会发现内存不足导致交换的问题。交换对于散列数组和随机键的效果非常糟糕。或者,如果您正在从慢速网络文件系统或旧USB记忆棒读取文件,您可能只是在等待I/O,尽管这种可能性较小


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

你说输入文件已排序,因此你可以大大改进你的
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
可以通过在
awk
命令中添加另一个
if
或追加
,删除开头的额外空行尾部-n+2

如果您的输入文件没有排序,这种方法速度很慢,即使使用
LC_ALL=C sort
排序速度更快(在我的系统上花费的时间只有
sort
的一半)


请注意,这只是对
awk
命令的改进。还可以从已排序的数据中获益,并击败
awk

文件有多大(以字节为单位)?排队吗?请问“大量时间”是什么?对于大文件,实现并行性的最佳方法是定义一个函数,将大文件拆分为多个块(不同的行范围),并在每个块中调用
awk
脚本@Mark:Size接近1 GB。线路数为1亿条。使用awk尝试运行了几次,但运行了几个小时却没有完成。看起来awk没有针对这么大的文件大小进行优化,因此我请求帮助添加parallel以进一步加快它。@Inian:如果我将文件拆分为小块,它可能会失去总体目标,因为它可能没有读取其他块,并且实际上没有计数。对于每个唯一的第一列,我们需要从所有文件中获取总数。如果我的逻辑不正确,请纠正我。你能提供关于第一列中键的更多信息吗?是只有几把钥匙,还是钥匙几乎和线一样多?在您的示例中,四个键重复。如果您知道键的数量,并且知道它们以相同的顺序重复,那么您就可以非常高效地计算所需的输出。在我的系统上,我在一个约12M行和676个键的文件上测试了前两个命令。结果非常不同<代码>awk需要5.2秒,但
datamash
需要9.0秒。结果可能也取决于键的数量,而不仅仅取决于行的数量。@Socowi,那么第三种方法呢?@Socowi,不。我认为你没有以正确的方式进行
parallel+awk
方法。它应该更快。您可以指定随机线生成场景,这样我就可以测试it@Socowi,我已下载并测试了您的文件
parallel+awk
是最快的。您可能会注意到我的更新
parallel+awk
现在对我来说也更快了。我不知道发生了什么变化。即使使用旧文件和旧命令
parallel+awk
现在也需要3.5秒。很抱歉给你带来了困惑。我绝对肯定一小时前花了5.2秒。无论如何,我的输入文件可能不具有代表性。OP说输入已排序
datamash
在未排序的文件上花费9秒,但在已排序的文件上仅花费2秒。
$ sort src | awk ...

a 2
b 3
c 4
d 5