这是一个非常有趣的问题。压缩是高度 CPU 密集型的,依赖于大量的搜索和比较。因此,当您拥有多个具有不受阻碍的内存访问的 CPU 时,想要并行化它是非常合适的。
有一个类叫做ParallelDeflateOutputStream
在执行您所描述的操作的 DotNetZip 库中。该类已记录here http://cheeso.members.winisp.net/DotNetZipHelp/html/26cbdba2-021a-ccf1-a9c9-b7ae55f6ecb8.htm.
它只能用于压缩 - 不能解压缩。而且它严格来说是一个输出流 - 你不能read
为了压缩。考虑到这些限制,它基本上是一个 DeflateOutputStream,内部使用多个线程。
它的工作方式:它将传入流分解为块,然后将每个块放入单独的工作线程中以进行单独压缩。然后,它最后将所有这些压缩流合并回一个有序流。
假设流维护的“块”大小是 N 字节。当调用者调用 Write() 时,数据被缓冲到存储桶或块中。在 - 的里面Stream.Write()
方法,当第一个“桶”满时,它调用ThreadPool.QueueUserWorkItem
,将存储桶分配给工作项。后续写入流开始填充下一个存储桶,当that已满,Stream.Write()
calls QUWI
再次。每个工作线程使用“刷新类型”压缩其存储桶Sync
(请参阅 deflate 规范),然后将其压缩 blob 标记为准备输出。然后,这些不同的输出被重新排序(因为块 n 不一定在块 n+1 之前被压缩),并写入捕获输出流。当每个桶被写入时,它被标记为空,准备好由下一个桶重新填充Stream.Write()
。每个块必须使用 Sync 的刷新类型进行压缩,以便允许它们通过简单的串联重新组合,使组合的字节流成为合法的 DEFLATE 流。最后的块需要齐平式= 完成。
该流的设计意味着调用者不需要使用多个线程编写。调用者只需像平常一样创建流,就像用于输出的普通 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 类中使用,但它作为独立的压缩输出流非常有用。生成的流可以用任何充气器去 DELFATED(充气?)。结果完全符合规范。
流是可调整的。您可以设置它使用的缓冲区的大小以及并行级别。它不会无限制地创建存储桶,因为对于大型流(GB 规模等),这会导致内存不足的情况。因此,存储桶的数量以及可支持的并行度都有固定的限制。
在我的双核机器上,与标准 DeflateStream 相比,该流类几乎使大型(100mb 及更大)文件的压缩速度提高了一倍。我没有更大的多核机器,所以我无法进一步测试它。代价是并行实现使用更多的 CPU 和更多的内存,并且由于我上面描述的同步帧,压缩效率也稍低(大文件减少 1%)。性能优势将根据输出流上的 I/O 吞吐量以及存储是否能跟上并行压缩器线程的速度而变化。
Caveat:
它是 DEFLATE 流,而不是 GZIP。对于差异,请阅读RFC 1951(放气) http://www.faqs.org/rfcs/rfc1951.html and
RFC 1952 (GZIP) http://www.faqs.org/rfcs/rfc1952.html.
但如果您确实需要 gzip,可以使用此流的源代码,因此您可以查看它,也许可以为自己获得一些想法。 GZIP 实际上只是 DEFLATE 之上的一个包装器,带有一些附加元数据(如 Adler 校验和等 - 请参阅规范)。在我看来,建立一个ParallelGzipOutputStream
,但这也可能不是微不足道的。
对我来说最棘手的部分是让 Flush() 和 Close() 的语义正常工作。
EDIT
只是为了好玩,我为 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
结论:
.NET 内置的 GZipStream 速度相当快。而且效率也不是很高,而且
它不可调。
DotNetZip 中的普通(非并行化)GZipStream 上的“BestSpeed”比 .NET 内置流快大约 20%,并且提供大约相同的压缩。
将普通的 DotNetZip GZipStream 与并行的相比,使用多个任务进行压缩可以将我的双核笔记本电脑(3GB RAM)所需的时间减少大约 45%。我想对于具有更多内核的机器来说,节省的时间会更多。
并行 GZIP 是有代价的 - 分帧会使压缩文件的大小增加约 4%。这不会随着使用的核心数量而改变。
生成的 .gz 文件可以通过任何 GZIP 工具解压缩。