多核 gzip 解压缩,将输出文件 (csv) 拆分为 1Gb/文件
Multicore gzip uncompression with spliting output file (csv) to parts by 1Gb/file
我有 10Gb gzip 存档(未压缩大约 60Gb)。
有没有办法通过多线程解压缩此文件 + 以 1Gb/部分(n-lines/part、maybe)即时将输出拆分为多个部分?
如果我这样做:
pigz -dc 60GB.csv.gz | dd bs=8M skip=0 count=512 of=4G-part-1.csv
我可以得到一个 4Gb 的文件,但它不关心总是从下一行开始,所以我的文件中的行不会正确结束。
此外,正如我所注意到的,我的带有永久磁盘的 GCE 实例最大块大小为 33kb,因此我实际上不能使用上面的命令,但必须打印如下内容:
pigz -dc 60GB.csv.gz | dd bs=1024 skip=0 count=4194304 of=4G-part-1.csv
pigz -dc 60GB.csv.gz | dd bs=1024 skip=4194304 count=4194304 of=4G-part-2.csv
pigz -dc 60GB.csv.gz | dd bs=1024 skip=$((4194304*2)) count=4194304 of=4G-part-3.csv
所以,我必须采取一些技巧来始终从新行开始文件..
更新:
zcat 60GB.csv.gz |awk 'NR%43000000==1{x="part-"++i".csv";}{print > x}'
成功了。
除非专门为这样的操作做准备,或者除非为此目的建立了索引,否则不会。 gzip 格式本质上要求在流中的任何点之前解压缩数据,以便在流中的该点之后解压缩数据。所以不能并行化。
出路是 a) 使用同步点重新压缩 gzip 文件并保存这些位置,或者 b) 遍历整个 gzip 文件一次并在这些点处使用先前的上下文创建另一个入口点文件。
对于 a),zlib 提供 Z_FULL_FLUSH
操作,可在流中插入同步点,您可以从中开始解压,而无需以前的历史记录。您可能希望谨慎地创建此类点,因为它们会降低压缩率。
对于 b),zran.c 提供了一个示例,说明如何将索引构建到 gzip 文件中。您需要按顺序通过流一次来构建索引,但是这样做之后,您可以在保存的位置开始解压缩。
根据您在问题中提到的尺寸,您似乎获得了大约 6 比 1 的压缩率。这似乎不太适合文本,但无论如何...
正如 Mark 所说,您不能只是将中流浸入 gz 文件并期望着陆在一个新行上。您的 dd
选项将不起作用,因为 dd 仅复制字节,它不会检测压缩的换行符。如果索引超出范围,以下命令行解决方案可能会有所帮助:
$ gzcat 60GB.csv.gz | awk -v n=1 '!NR%20000000{n++} {print|("gzip>part-"n".gz")}'
这会解压缩您的文件,以便我们计算行数,然后处理流,每 20000000 行更改一次输出文件名。您可以在上面代码中看到 "gzip" 的地方调整重新压缩选项。
如果您不想压缩输出,可以简化该行的最后一部分:
$ gzcat 60GB.csv.gz | awk -v n=1 '!NR%3500000{n++} {print>("part-"n".csv")}'
您可能需要调整行数以获得接近您目标的文件大小。
请注意,如果您的 shell 是 csh/tcsh,您可能必须在 awk 脚本中转义感叹号以避免它被解释为历史参考。
更新:
如果您想了解脚本正在执行的操作的状态,awk 可以做到。像这样的东西可能很有趣:
$ gzcat 60GB.csv.gz | awk -v n=1 '!NR%3500000{n++} !NR%1000{printf("part=%d / line=%d\r",n,NR)} {print>("part-"n".csv")}'
这应该每千行显示当前部分和行号。
我有 10Gb gzip 存档(未压缩大约 60Gb)。
有没有办法通过多线程解压缩此文件 + 以 1Gb/部分(n-lines/part、maybe)即时将输出拆分为多个部分?
如果我这样做:
pigz -dc 60GB.csv.gz | dd bs=8M skip=0 count=512 of=4G-part-1.csv
我可以得到一个 4Gb 的文件,但它不关心总是从下一行开始,所以我的文件中的行不会正确结束。
此外,正如我所注意到的,我的带有永久磁盘的 GCE 实例最大块大小为 33kb,因此我实际上不能使用上面的命令,但必须打印如下内容:
pigz -dc 60GB.csv.gz | dd bs=1024 skip=0 count=4194304 of=4G-part-1.csv
pigz -dc 60GB.csv.gz | dd bs=1024 skip=4194304 count=4194304 of=4G-part-2.csv
pigz -dc 60GB.csv.gz | dd bs=1024 skip=$((4194304*2)) count=4194304 of=4G-part-3.csv
所以,我必须采取一些技巧来始终从新行开始文件..
更新:
zcat 60GB.csv.gz |awk 'NR%43000000==1{x="part-"++i".csv";}{print > x}'
成功了。
除非专门为这样的操作做准备,或者除非为此目的建立了索引,否则不会。 gzip 格式本质上要求在流中的任何点之前解压缩数据,以便在流中的该点之后解压缩数据。所以不能并行化。
出路是 a) 使用同步点重新压缩 gzip 文件并保存这些位置,或者 b) 遍历整个 gzip 文件一次并在这些点处使用先前的上下文创建另一个入口点文件。
对于 a),zlib 提供 Z_FULL_FLUSH
操作,可在流中插入同步点,您可以从中开始解压,而无需以前的历史记录。您可能希望谨慎地创建此类点,因为它们会降低压缩率。
对于 b),zran.c 提供了一个示例,说明如何将索引构建到 gzip 文件中。您需要按顺序通过流一次来构建索引,但是这样做之后,您可以在保存的位置开始解压缩。
根据您在问题中提到的尺寸,您似乎获得了大约 6 比 1 的压缩率。这似乎不太适合文本,但无论如何...
正如 Mark 所说,您不能只是将中流浸入 gz 文件并期望着陆在一个新行上。您的 dd
选项将不起作用,因为 dd 仅复制字节,它不会检测压缩的换行符。如果索引超出范围,以下命令行解决方案可能会有所帮助:
$ gzcat 60GB.csv.gz | awk -v n=1 '!NR%20000000{n++} {print|("gzip>part-"n".gz")}'
这会解压缩您的文件,以便我们计算行数,然后处理流,每 20000000 行更改一次输出文件名。您可以在上面代码中看到 "gzip" 的地方调整重新压缩选项。
如果您不想压缩输出,可以简化该行的最后一部分:
$ gzcat 60GB.csv.gz | awk -v n=1 '!NR%3500000{n++} {print>("part-"n".csv")}'
您可能需要调整行数以获得接近您目标的文件大小。
请注意,如果您的 shell 是 csh/tcsh,您可能必须在 awk 脚本中转义感叹号以避免它被解释为历史参考。
更新:
如果您想了解脚本正在执行的操作的状态,awk 可以做到。像这样的东西可能很有趣:
$ gzcat 60GB.csv.gz | awk -v n=1 '!NR%3500000{n++} !NR%1000{printf("part=%d / line=%d\r",n,NR)} {print>("part-"n".csv")}'
这应该每千行显示当前部分和行号。