如何使用Python实现并行gzip压缩?

14

标准库中的压缩模块不是纯Python的。如果你仔细研究它们,你会发现它们是与共享库(用C编写)接口的。 - Roland Smith
4
也许是时候淘汰使用gzip压缩了。新的zstd压缩通常比gzip更快,且生成的压缩文件更小。 - Roland Smith
1
@RolandSmith:当然,它也没有Python接口。它似乎比gzip更快,但有许多选项可以“压缩得更快”。gzip之所以仍然存在,至少部分原因是由于兼容性问题;您可以在具有10年以上硬件/软件的系统上解压缩它,并且它可能已默认安装(bz2几乎同样普及,xz正在逐渐普及)。对于将数据分发给许多方,可移植性和压缩比比速度更重要。对于短暂的压缩,速度通常胜过压缩比,因此lz4lzo可能会胜过zstd - ShadowRanger
基本上,如果您不受兼容性约束(您可以假设他们拥有软件和一些最小的RAM),则应分发打包数据(压缩一次,解压多次)使用 xz 压缩,并对按需压缩的数据使用 lz4/snappy/lzo,其中更快的压缩意味着数据到达得更快,具有“足够好”的压缩。 - ShadowRanger
@RolandSmith 是的,没错。我的意思是它们将是 Python 代码,而不是例如针对文件系统上的其他二进制文件的 shellex。 - Virgil Gheorghiu
3个回答

8

mgzip 能够实现这一点。

使用块索引的 GZIP 文件格式,以实现并行压缩和解压缩。该实现使用 'FEXTRA' 记录压缩成员的索引,它在官方 GZIP 文件格式规范版本 4.3 中已定义,因此与普通的 GZIP 实现完全兼容。

import mgzip

num_cpus = 0 # will use all available CPUs

with open('original_file.txt', 'rb') as original, mgzip.open(
    'gzipped_file.txt.gz', 'wb', thread=num_cpus, blocksize=2 * 10 ** 8
) as fw:
    fw.write(original.read())

我成功将72个CPU服务器上的压缩时间从45分钟缩短至5分钟。


2
请注意,来自同一作者的新版 pgzip 取代了 mgzipmgzip 自 2020 年以来没有得到更新。https://github.com/pgzip/pgzip - OrderFromChaos

6
我不知道Python是否有pigz接口,但如果您真的需要,编写它可能并不难。Python的zlib模块允许压缩任意字节块,pigz手册已经描述了并行压缩和输出格式的系统。
如果您确实需要并行压缩,可以使用zlib来压缩通过multiprocessing.dummy.Pool.imap包装的块(multiprocessing.dummymultiprocessing API的线程支持版本,因此发送块到工作进程并从中接收数据不会产生巨大的IPC成本)以并行压缩。由于zlib是少数几个在CPU密集型工作期间释放GIL的内置模块之一,因此您可能会从基于线程的并行性中获得好处。
请注意,在实践中,当压缩级别不太高时,I/O的成本通常与实际的zlib压缩相似(在数量级左右);如果您的数据源无法比线程更快地提供数据,则并行化不会带来太多收益。

你不必将数据块发送给工作进程。只需让每个工作进程从文件中读取自己的数据块即可。或者在UNIX上,在创建进程池之前可以为输入创建一个内存映射文件。操作系统的虚拟内存系统将会承担大部分工作,以保持输入文件的页面在内存中。 - Roland Smith
@RolandSmith: 没错。我是mmap的忠实粉丝,而且zlib.compress似乎支持缓冲区协议(也就是说,它可以从mmapmemoryview中读取数据以避免复制数据)。你仍然需要imap来协调工作线程拉取块并组织输出(由于无法预测压缩块的大小,因此最好对写入进行序列化)。 - ShadowRanger
关于协调,我会创建一个每个128 kB块的起始字节偏移量列表,并在其上进行imap。至于输出,我可能会将每个压缩块写入临时输出文件,然后稍后将它们连接起来。或者也可以尝试使用mmap。将其传回父进程感觉不太优化。 - Roland Smith
@RolandSmith:这就是为什么我建议使用线程池而不是进程池。将其从线程工作器传递回主线程非常便宜,没有涉及到任何复制。 - ShadowRanger
看到哪种方法更快肯定会很有趣。 :-) - Roland Smith

2
您可以使用flush()操作和Z_SYNC_FLUSH,以完成最后一个deflate块,并在字节边界结束它。您可以将这些连接起来以生成有效的deflate流,只要您连接的最后一个被刷新为Z_FINISH(这是flush()的默认值)。
您还需要并行计算CRC-32(无论是zip还是gzip - 我想您真正意思是并行gzip压缩)。Python没有提供与zlib的函数的接口。但是您可以从zlib复制代码并将其转换为Python。那样速度就足够快了,因为它不需要经常运行。此外,您可以预先构建所需的表以使其更快,甚至可以预先构建固定块长度的矩阵。

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