4

我们目前正在使用 ICsharpCode.SharpZipLib 库的 GZipOutputStream 类进行压缩。我们从一个线程中完成。

我想将我的输入数据流拆分成块并并行压缩它们。我很担心这个库里面可能有一些静态数据,这些静态数据会被多个线程覆盖,因此会破坏生成的流。

任何想法将不胜感激。

4

3 回答 3

11

这是一个非常有趣的问题。压缩是高度 CPU 密集型的,依赖于大量搜索和比较。因此,当您拥有多个 CPU 且内存访问不受阻碍时,想要并行化它是非常合适的。

DotNetZip 库中调用了一个类ParallelDeflateOutputStream来执行您所描述的操作。该类记录在这里

它只能用于压缩 - 不能解压缩。此外,它严格来说是一个输出流——你不能read为了压缩。考虑到这些约束,它基本上是一个 DeflateOutputStream,内部使用多个线程。

它的工作方式:它将传入的流分解成块,然后将每个块放入单独的工作线程中进行单独压缩。然后它将所有这些压缩流合并回最后一个有序流。

假设流维护的“块”大小为 N 字节。当调用者调用 Write() 时,数据被缓冲到存储桶或块中。在该Stream.Write()方法内部,当第一个“桶”已满时,它会调用ThreadPool.QueueUserWorkItem,将桶分配给工作项。随后对流的写入开始填充下一个存储桶,当已满时,再次Stream.Write()调用QUWI。每个工作线程使用“刷新类型” Sync(请参阅​​ deflate 规范)压缩其存储桶,然后将其压缩的 blob 标记为准备好输出。然后对这些不同的输出重新排序(因为块 n 不一定在块 n+1 之前被压缩),并写入强制输出流。当每个桶被写入时,它被标记为空,准备好被下一个桶重新填充Stream.Write(). 每个块必须使用 Sync 的刷新类型进行压缩,以允许通过简单的连接重新组合它们,使组合的字节流成为合法的 DEFLATE 流。最后的块需要Flush type = Finish。

这个流的设计意味着调用者不需要用多个线程来写。 调用者只需像平常一样创建流,就像用于输出的普通 DeflateStream 一样,然后写入它。流对象使用多个线程,但您的代码不直接与它们交互。“用户”的代码ParallelDeflateOutputStream如下所示:

using (FileStream raw = new FileStream(CompressedFile, FileMode.Create))
{
    using (FileStream input = File.OpenRead(FileToCompress))
    {
        using (var compressor = new Ionic.Zlib.ParallelDeflateOutputStream(raw))
        {
            // could tweak params of parallel deflater here
            int n;
            var buffer = new byte[8192];
            while ((n = input.Read(buffer, 0, buffer.Length)) != 0)
            {
                compressor.Write(buffer, 0, n);
            }                    
        }
    }
}

它是为在 DotNetZip ZipFile 类中使用而设计的,但它非常适合用作独立的压缩输出流。生成的流可以使用任何充气机进行 de-DELFATED(充气?)。结果完全符合规范。

流是可调整的。您可以设置它使用的缓冲区大小和并行度。它不会无限制地创建存储桶,因为对于大型流(gb 规模等)会导致内存不足的情况。所以对桶的数量有一个固定的限制,因此可以支持的并行度也有一个限制。

在我的双核机器上,与标准的 DeflateStream 相比,这个流类几乎将大型(100mb 和更大)文件的压缩速度提高了一倍。我没有任何更大的多核机器,所以我无法进一步测试它。权衡是并行实现使用更多的 CPU 和更多的内存,并且由于我上面描述的同步帧,压缩效率略低(大文件减少 1%)。性能优势取决于输出流上的 I/O 吞吐量,以及存储是否能够跟上并行压缩线程的速度。


警告:
这是一个 DEFLATE 流,而不是 GZIP。有关差异,请阅读RFC 1951 (DEFLATE)RFC 1952 (GZIP)

但是如果你真的需要 gzip,这个流的源是可用的,所以你可以查看它,也许你自己会得到一些想法。GZIP 实际上只是 DEFLATE 之上的一个包装器,带有一些额外的元数据(如 Adler 校验和等 - 请参阅规范)。在我看来,构建一个 .com 并不是很困难ParallelGzipOutputStream,但也可能不是微不足道的。

对我来说最棘手的部分是让 Flush() 和 Close() 的语义正常工作。


编辑

只是为了好玩,我为 GZip 构建了一个 ParallelGZipOutputStream,它基本上完成了我上面描述的操作。它使用 .NET 4.0 的任务代替 QUWI 来处理并行压缩。我刚刚在通过马尔可夫链引擎生成的 100mb 文本文件上对其进行了测试。我将该课程的结果与其他一些选项进行了比较。这是它的样子:

uncompressed: 104857600
running 2 cycles, 6 Flavors

System.IO.Compression.GZipStream:  .NET 2.0 builtin
  compressed: 47550941
  ratio     : 54.65%
  Elapsed   : 19.22s

ICSharpCode.SharpZipLib.GZip.GZipOutputStream:  0.86.0.518
  compressed: 37894303
  ratio     : 63.86%
  Elapsed   : 36.43s

Ionic.Zlib.GZipStream:  DotNetZip v1.9.1.5, CompLevel=Default
  compressed: 37896198
  ratio     : 63.86%
  Elapsed   : 39.12s

Ionic.Zlib.GZipStream:  DotNetZip v1.9.1.5, CompLevel=BestSpeed
  compressed: 47204891
  ratio     : 54.98%
  Elapsed   : 15.19s

Ionic.Exploration.ParallelGZipOutputStream: DotNetZip v1.9.1.5, CompLevel=Default
  compressed: 39524723
  ratio     : 62.31%
  Elapsed   : 20.98s

Ionic.Exploration.ParallelGZipOutputStream:DotNetZip v1.9.1.5, CompLevel=BestSpeed
  compressed: 47937903
  ratio     : 54.28%
  Elapsed   : 9.42s

结论:

  1. .NET 内置的 GZipStream 非常快。它也不是很有效,而且它是不可调整的。

  2. DotNetZip 中的普通(非并行)GZipStream 上的“BestSpeed”比 .NET 内置流快约 20%,并提供大致相同的压缩。

  3. 与普通的 DotNetZip GZipStream 和并行的相比,使用多个任务进行压缩可以将我的双核笔记本电脑(3gb RAM)所需的时间减少约 45%。我想对于具有更多内核的机器来说,节省的时间会更高。

  4. 并行 GZIP 是有成本的——分帧将压缩文件的大小增加了大约 4%。这不会随着使用的核心数量而改变。

生成的 .gz 文件可以通过任何 GZIP 工具解压缩。

于 2011-05-06T17:10:15.900 回答
1

我的理解是 zip 正在写入(或读取)单个底层流;所以我的假设将是一个响亮的否定;如果您谈论的是单个底层流,这不可能是线程安全的。

然而; 单独的实例与单独的底层流对话应该没问题;实际上,并行运行单独的(不相关的)任务通常比并行化单个任务更容易。

于 2011-05-05T16:47:19.257 回答
0

在编写类时,确保所有静态成员都是线程安全的是标准做法。所以我认为你不太可能因为这个问题而遇到问题。当然,如果你打算在不同的线程中使用相同 GZipOutputStream的,那肯定会有问题,因为该类的实例成员不是线程安全的。

您可能能够做的是创建一个线程安全的中间Stream人类(想想装饰器模式)并将其传递给GZipOutputStream. 这个自定义流类(称为它ThreadSafeStream)本身会接受一个Stream实例,并会使用适当的机制来同步对它的访问。

您将为GZipOutputStream每个线程创建一个实例,它们都将共享相同的ThreadSafeStream包装器实例。我怀疑这些方法可能会有很多瓶颈ThreadSafeStream,但是您应该能够从中获得一些并行性。

于 2011-05-05T18:42:19.653 回答