GNU Parallel:将文件拆分为子文

时间:2017-01-17 18:02:25

标签: bash parallel-processing gnu-parallel

目标

使用GNU Parallel将大型.gz文件拆分为子文件。由于服务器有16个CPU,因此创建16个子节点。每个孩子最多应包含N行。这里,N = 104,214,420行。儿童应该是.gz格式。

输入文件

  • name:file1.fastq.gz
  • 尺寸:39 GB
  • 行数:1,667,430,708(未压缩)

硬件

  • 36 GB内存
  • 16个CPU
  • HPCC环境(我不是管理员)

代码

版本1

zcat "${input_file}" | parallel --pipe -N 104214420 --joblog split_log.txt --resume-failed "gzip > ${input_file}_child_{#}.gz"

三天后,工作没有完成。 split_log.txt为空。输出目录中没有可见的子项。日志文件表明Parallel已将--block-size从1 MB(默认值)增加到2 GB以上。这激发了我将代码更改为版本2.

第2版

# --block-size 3000000000 means a single record could be 3 GB long. Parallel will increase this value if needed.

zcat "${input_file}" | "${parallel}" --pipe -N 104214420 --block-size 3000000000 --joblog split_log.txt --resume-failed "gzip > ${input_file}_child_{#}.gz"

这份工作已经运行了大约2个小时。 split_log.txt为空。尚未在输出目录中看到子项。到目前为止,日志文件显示以下警告:

parallel: Warning: --blocksize >= 2G causes problems. Using 2G-1.

问题

  1. 我的代码如何改进?
  2. 有没有更快的方法来实现这一目标?

2 个答案:

答案 0 :(得分:2)

我们假设该文件是一个fastq文件,因此记录大小为4行。

你告诉GNU Parallel -L 4

在fastq文件中,顺序无关紧要,因此您希望将n * 4行的块传递给子项。

要有效地执行此操作,请使用--pipe-part,但--pipe-part不适用于压缩文件且不适用于-L,因此您必须选择--pipe

zcat file1.fastq.gz | parallel -j16 --pipe -L 4 --joblog split_log.txt --resume-failed "gzip > ${input_file}_child_{#}.gz"

这会将一个块传递给16个子节点,并且一个块默认为1 MB,它在记录边界(即4行)处被切断。它将为每个块运行一个作业。但你真正想要的是将输入传递给总共只有16个工作,你可以这样做循环。遗憾的是,--round-robin中存在随机元素,因此--resume-failed无效:

zcat file1.fastq.gz | parallel -j16 --pipe -L 4 --joblog split_log.txt --round-robin "gzip > ${input_file}_child_{#}.gz"

parallel将努力跟上16 gzips,但你应该能够压缩100-200 MB / s。

现在如果你有fastq文件未压缩我们可以更快地完成它,但我们将不得不作弊:通常在fastq文件中你将有一个seqname,它启动相同的字符串:

@EAS54_6_R1_2_1_413_324
CCCTTCTTGTCTTCAGCGTTTCTCC
+
;;3;;;;;;;;;;;;7;;;;;;;88
@EAS54_6_R1_2_1_540_792
TTGGCAGGCCAAGGCCGATGGATCA
+
;;;;;;;;;;;7;;;;;-;;;3;83
@EAS54_6_R1_2_1_443_348
GTTGCTTCTGGCGTGGGTGGGGGGG
+EAS54_6_R1_2_1_443_348
;;;;;;;;;;;9;7;;.7;393333

这是@EAS54_6_R。不幸的是,这也是质量线中的有效字符串(这是真正的哑设计),但实际上,我们会非常惊讶地看到以@EAS54_6_R开头的质量线。它不会发生。

我们可以使用它,因为现在您可以使用\n后跟@EAS54_6_R作为记录分隔符,然后我们可以使用--pipe-part。额外的好处是订单将保持不变。在这里,您必须将块大小设置为file1-fastq的大小的1/16:

parallel -a file1.fastq --block <<1/16th of the size of file1.fastq>> -j16 --pipe-part --recend '\n' --recstart '@EAS54_6_R' --joblog split_log.txt "gzip > ${input_file}_child_{#}.gz"

如果你使用GNU Parallel 20161222,那么GNU Parallel可以为你做这个计算。 --block -1表示:选择一个块大小,以便您可以为16个作业点中的每一个提供一个块。

parallel -a file1.fastq --block -1 -j16 --pipe-part --recend '\n' --recstart '@EAS54_6_R' --joblog split_log.txt "gzip > ${input_file}_child_{#}.gz"

这里GNU Parallel不是限制因素:它可以轻松传输20 GB / s。

打开文件以查看重新启动值应该是什么很烦人,所以这在大多数情况下都有效:

parallel -a file1.fastq --pipe-part --block -1 -j16 
--regexp --recend '\n' --recstart '@.*\n[A-Za-z\n\.~]'
my_command

这里我们假设这些行将像这样开始:

@
[A-Za-z\n\.~]
anything
anything

即使您有一些以'@'开头的质量线,那么它们也永远不会出现以[A-Za-z \ n。〜]开头的行,因为质量线后面总是跟着seqname行,以@。

开头

你也可能有一个块大小,它相当于未压缩文件的1/16,但这是一个坏主意:

  • 您必须能够将完整的未压缩文件保存在RAM中。
  • 最后一个gzip只会在读完最后一个字节后启动(并且当时可能会完成第一个gzip)。

通过将记录数设置为104214420(使用-N),这基本上就是你正在做的事情,而你的服务器可能很难将150 GB的未压缩数据保存在36 GB的RAM中。

答案 1 :(得分:1)

配对结束有限制:订单无关紧要,但订单必须可以预测不同的文件。例如。 file1.r1.fastq.gz中的record n必须与file1.r2.fastq.gz中的记录n匹配。

split -n r/16对于执行简单的循环非常有效。但是,它不支持多行记录。所以我们在每第4行之后插入\ 0作为记录分隔符,我们在拆分后删除它。 --filter在输入上运行命令,因此我们不需要保存未压缩的数据:

doit() { perl -pe 's/\0//' | gzip > $FILE.gz; }
export -f doit
zcat big.gz | perl -pe '($.-1)%4 or print "\0"' | split -t '\0' -n r/16 --filter doit - big.

文件名将命名为big.aa.gz .. big.ap.gz