GNU Parallel:将文件分割成子文件

7

目标

使用GNU Parallel将一个大的.gz文件分成若干个子文件。由于服务器有16个CPU,因此需要创建16个子进程。每个子进程应该包含不超过N行。这里,N = 104,214,420行。子进程应该以.gz格式存储。

输入文件

  • 名称: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. 有更快的方法来实现这个目标吗?

不确定我是否理解了您的输入文件。如果您的文件必须每个文件有1.04亿行,因为您有16个CPU,那么我推断您的输入文件有16亿行。然后您说记录每个文件有3GB,因此您有160亿条记录,每个记录压缩成39GB的文件。我有点想要那种压缩算法 :-) 请告知我哪一部分我误解了! - Mark Setchell
@MarkSetchell:file1.fastq.gz(39 GB)包含1,667,430,708行。它的子文件应该每个最多包含104,214,420行。老实说,我不知道最大行/记录的大小。我选择了--block-size 3000000000是因为注意到Parallel将大小从1 MB(默认值)增加到超过2 GB。我觉得3 GB应该是安全的。请指教 :) - fire_water
抱歉,我无法启发您 - 我也是没有启发的 :-( 只是在尝试理解。我认为我们可能需要等待 Ole 来启发我们所有人 :-) - Mark Setchell
这个在Super User上的答案(http://superuser.com/a/906756/2085)可能会有所帮助。 - Sean Bright
@SeanBright:该文件未压缩时大约为150GB(gzip -dc file1.fastq.gz | wc -c)。 - fire_water
2个回答

4
假设文件是一个fastq文件,因此记录大小为4行。您可以使用“-L 4”参数告诉GNU Parallel。
在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个gzip的速度,但你应该能够以100-200 MB/s的速度压缩。

现在,如果您的fastq文件未经压缩,我们甚至可以更快地完成任务,但我们需要偷懒一点:通常在fastq文件中,您将拥有一个以相同字符串开头的序列名称:

@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。

需要打开文件才能查看recstart值真是令人恼火,所以在大多数情况下,以下方法适用:

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

在这里我们假设每行都是以这样的方式开始:

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

即使您有一些以“@”开头的优质行,它们也永远不会被以 [A-Za-z\n.~] 开头的行跟随,因为优质行总是紧随序列名称行后面,序列名称行以“@”开头。
您还可以设置块大小,使其与未压缩文件的1/16相对应,但这是一个糟糕的想法:
  • 您必须能够在RAM中保留完整的未压缩文件。
  • 只有在读取了最后一个字节之后(第一个gzip可能已经完成),才会启动最后一个 gzip
通过将记录数设置为104214420(使用-N),这基本上就是您所做的,在36 GB的RAM中保持150 GB未压缩数据,可能会使服务器出现问题。

1
你是一位学者和智者。谢谢!问题:假设我正在对FASTQ文件进行成对处理。例如,在成对末端测序(一种生物信息学术语)中,我们有两个FASTQ文件:file1.r1.fastq.gz和file1.r2.fastq.gz。在这里,顺序很重要:r1文件中的第一个记录与r2文件中的第一个记录成对,依此类推,在后续分析中也是如此。为了适应这种情况,Parallel命令会是什么样子? - fire_water

1
Paired end有一个限制:顺序无关紧要,但对于不同的文件,顺序必须是可预测的。例如,file1.r1.fastq.gz中的记录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

谢谢,我现在才看到这个消息。昨天我在玩耍,写了一些代码想要展示给你看:parallel zcat {} '|' split -l ${child_num_lines} --filter=''gzip > $FILE.gz'' - ${temp_dir}/{}_ ::: "${r1_fastq_gz}" "${r2_fastq_gz}" 这段代码实现的功能和你的代码一样吗?只不过用了另一种方式,可能会更慢,但仍然保持可预测的顺序。 - fire_water

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接