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