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

Best expression using parallel for sorting a large text file and printing the sum of the second column

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

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

使用基本的 awk,我能够获得所需的输出。

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

手头的问题是处理 运行s 的时间很长,如果我们 运行 与大型输入文件相同。因此尝试 运行 与 gnu-parallel 相同并在那里进行攻击。

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

任何关于这方面的指导将不胜感激。

我发现 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[] +=  }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[] +=  }END{ for(i in a) print i, a[i] }' \
| awk '{ a[] +=  }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 被用作最后一种方法,由 2awk 命令的组合组成(一个用于聚合中间结果,另一个用于聚合最终结果)。 这里关键的 GNU parallel 选项是:

--pipepart
Pipe parts of a physical file. --pipepart works similar to --pipe, but is much faster.

--block-size size
Size of block in bytes to read at a time.

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

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

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

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

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

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

此命令使用恒定数量的内存,而不是键数的线性数量。由于 ,在您的情况下,内存可能是主要的减速因素。

由于您的示例文件未排序,我们在 sort

之后在管道中测试命令
$ sort src | awk ...

a 2
b 3
c 4
d 5

可以通过在 awk 命令中添加另一个 if 或附加 ... | tail -n +2.

来删除开头的额外空行

如果您的输入文件未排序,这种方法很慢,即使使用 LC_ALL=C sort 排序更快(与 sort 相比,我的系统花费一半的时间)。

请注意,这只是 awk 命令的改进。 也受益于已经排序的数据并击败 awk.