我希望能够使用并发CPU线程生成gzip(.gz)文件。即,我将使用单独初始化的z_stream
记录从输入文件中缩小单独的块。
在经典的单线程操作中,zlib的inflate()函数可以读取生成的文件。
这可能吗?即使它需要定制的zlib代码?唯一的要求是当前存在的zlib的inflate代码可以处理它。
更新
pigz源代码演示了它的工作原理。它使用一些复杂的优化来在块之间共享字典,从而保持压缩率最佳。如果使用更新的zlib版本,它还会处理位打包。
但是,如果没有pigz
使用的优化,我想了解如何自己动手,保持简单。
虽然许多人认为源代码是最终文档(Ed Post, anyone?),但我宁愿用简单的词语解释它以避免误解。 (虽然文档实际上描述了发生得非常好的事情,但他们并没有很好地解释需要做些什么来推动自己的事情。)
从浏览代码开始,我到目前为止已经找到了这个:
似乎只使用deflate(..., Z_SYNC_FLUSH)
创建每个压缩块而不是使用Z_FINISH
。但是,deflateEnd()
会出错,不确定是否可以忽略。并且需要手动计算所有块的最终校验和,但我想知道如何在最后添加校验和。还有一个相当复杂的put_trailer()
函数用于编写gzip头 - 我想知道zlib自己的代码是否也可以用简单的情况来处理它?</ p>
对此有任何澄清表示赞赏。
另外,我意识到我应该以同样的方式包括询问编写zlib流,以便将多线程压缩文件写入zip存档。在那里,我怀疑,由于缺少更复杂的gzip标题,可能会有更多的简化。
答案 0 :(得分:3)
答案在你的问题中。每个线程都有自己的deflate
实例来生成原始deflate数据(请参阅deflateInit2()
),该数据压缩馈送给它的数据块,以Z_SYNC_FLUSH
而不是Z_FINISH
结尾。除了以Z_FINISH
结尾的最后一块数据。无论哪种方式,这都会在字节边界上结束每个得到的压缩数据流。确保从deflate()
中获取所有生成的数据。然后,您可以连接所有压缩数据流。 (按照正确的顺序!)使用您自己生成的gzip标头。这样做很简单(见RFC 1952)。如果您不需要标题中包含任何其他信息(例如文件名,修改日期),它可以只是一个10字节的常量序列。 gzip标头并不复杂。
您还可以在同一个线程或不同的线程中计算每个未压缩块的CRC-32,并使用crc32_combine()
组合这些CRC-32。你需要gzip预告片。
写完所有压缩流后,以Z_FINISH
结束的压缩流结束,然后附加gzip预告片。所有这一切都是四字节CRC-32和总未压缩长度的低四字节,都是小端序。总共八个字节。
在每个线程中,您可以在完成每个块时使用deflateEnd()
,或者如果要重新使用线程获取更多块,请使用deflateReset()
。我在pigz中发现,在处理多个块时,保持线程打开并在其中打开deflate
实例效率更高。在关闭线程之前,确保使用deflateEnd()
作为线程处理的最后一个块。是的,可以忽略来自deflateEnd()
的错误。只需确保您运行deflate()
,直到avail_out
不为零才能获取所有压缩数据。
这样做,每个线程压缩其块而不引用任何其他未压缩数据,这些引用通常会在串行执行时改进压缩。如果您想获得更高级的功能,可以为每个线程提供要压缩的未压缩数据块,和上一个块的最后32K,以便为压缩器提供历史记录。您可以使用deflateSetDictionary()
执行此操作。
更高级的是,您可以通过有时使用Z_PARTIAL_FLUSH
来减少压缩流之间插入的字节数,直到达到字节边界。有关详细信息,请参阅pigz。
更高级但速度更慢,您可以在位级别而不是字节级别附加压缩流。这将需要将压缩流的每个字节移位两次以构建新的移位流。每八个先前压缩流中至少有七个。这消除了在压缩流之间插入的所有额外位。
zlib流可以完全相同的方式生成,使用adler32_combine()
作为校验和。
关于zlib的问题意味着混乱。 zip格式不使用zlib标头和预告片。 zip has its own structure,其中嵌入了原始的deflate流。您也可以将上述方法用于那些原始的deflate流。
答案 1 :(得分:1)