在很多情况下,需要用 Zip 格式保存数据文档。当数据量较大时(超过100M),就会明显感觉到保存文件耗时很长(超过10秒),这样的用户体验难以接受,想要优化。
现有开源方案
zlib-ng/zlib-ng | 支持特殊指令提升效率 | 下一代 zlib,需要集成到 gzip 中 |
pigz - Parallel gzip | 多线程并行提高效率 | 只支持命令行模式允许,没有 API |
miniZ | 支持 GPU | 商用,需要收费 |
综上,没有特别满足需求的开源方案,所以下面介绍一个简单的多线程压缩方案。
多线程多文件方案
我们的多线程压缩方案一个重要前提是:需要压缩多个文件,保存到一个 zip 中。
该方案不是特别依赖某一个 gzip 开发库,我们用的是 minz,只是作为一个例子,如果使用其他 gzip 开发库,也应该很容易基于它实现该方案。
该方案的核心思想,是将每个文件的压缩作为一个任务,每个任务生成一个包含单文件的 zip 包(在内存中生成,不一定要写文件),然后合并到主 zip 包中。任务可以在线程池中并行执行,只是合并时,需要串行执行。
即使多了一些内存拷贝操作,最后的步骤也必须串行执行,但是因为大部分消耗是在压缩算法上,最终的效果还是很不错的,我的 6核(12逻辑核心)的 CPU,也能够将时间减少到原来的 1/10。
方案实现
并行执行
使用 Threading Building Blocks (TBB),可以极大简化并行任务调度
std::vector<std::string> files;
boost::mutex mutex;
tbb::parallel_for(tbb::blocked_range<size_t>(0, files.size(), 1),
[&files, &root_zip, &mutex](const tbb::blocked_range<size_t>& range) {
for (int i = range.begin(); i < range.end(); ++i) {
auto file = files[i];
// 压缩 file 到 zip
{
boost::unique_lock l(mutex);
// 合并 zip 到 root_zip
}
}
});
这只是 tbb 的小试牛刀。
压缩 zip
// 压缩到内存
mz_zip_archive archive;
mz_zip_writer_staged_context context;
mz_zip_zero_struct(&archive);
mz_zip_writer_init_heap(&archive, 0, 1024 * 1024);
mz_zip_writer_add_file(&archive, file.c_str(), file.c_str());
// 取出内存指针
void *ppBuf; size_t pSize;
mz_zip_writer_finalize_heap_archive(&archive, &ppBuf, &pSize);
mz_zip_writer_end(&archive);
// 用读模式重新打开
mz_zip_zero_struct(&archive);
mz_zip_reader_init_mem(&archive, ppBuf, pSize, 0);
合并 zip
{
boost::unique_lock l(mutex);
mz_zip_writer_add_from_zip_reader(&root_archive, &archive, 0);
}
mz_zip_reader_end(&archive);