- 桌面应用
- CLI
【免费下载链接】7-Zip-zstd
7-Zip with support for Brotli, Fast-LZMA2, Lizard, LZ4, LZ5 and Zstandard
本篇文章以 C/zstdmt/README.md 为核心骨架,系统讲解 7-Zip-zstd 项目中为 Brotli、Lizard、LZ4、LZ5 与 Zstandard 五种压缩算法提供的多线程(MT)封装库 zstdmt:其基于 Zstandard Skippable Frame(0x184D2A50)的容器化帧格式、Brotli 特有的 16 字节扩展帧头、库的公开 API 与读/写回调约定,以及仓库源码中的多线程工作模型与 7-Zip 集成方式。读完本文,你将掌握该 MT 容器在字节层面上的精确布局、各算法的线程数与压缩级别取值范围,并能基于 C/zstdmt 下的头文件独立接入这套多线程压缩/解压接口。
一、zstdmt 是什么:五种算法的统一多线程封装
Brotli、Lizard、LZ4、LZ5 和 Zstandard 的参考实现本身大多是单线程的:压缩速度受限于单核。zstdmt 是 Tino Reichardt 编写的一层薄封装,它把每种算法的"单次压缩单元"(frame)按固定大小的输入块切分,用多个 worker 线程并行压缩,再把结果按 Zstandard 的 Skippable Frame 容器格式串联成一个合法、可被标准解码器识别为"可跳过帧"的流,从而实现多线程压缩与多线程解压。
从仓库布局看,这五种算法的 MT 封装各有一套对称的文件族(common 错误处理、compress、decompress)与对应的公共头文件:
| 算法 | 公共头文件 | 压缩实现 | 解压实现 | 底层算法源码 |
|---|---|---|---|---|
| Brotli | brotli-mt.h | brotli-mt_compress.c | brotli-mt_decompress.c | C/brotli |
| Lizard | lizard-mt.h | lizard-mt_compress.c | lizard-mt_decompress.c | C/lizard |
| LZ4 | lz4-mt.h | lz4-mt_compress.c | lz4-mt_decompress.c | C/lz4 |
| LZ5 | lz5-mt.h | lz5-mt_compress.c | lz5-mt_decompress.c | C/lz5 |
| Zstandard | —(使用官方 mt API) | C/zstd/zstdmt_compress.c | 同左 | C/zstd |
其中 Zstandard 的多线程能力直接复用其官方ZSTDMT_*API(C/zstd/zstdmt_compress.c 与 C/zstd/zstdmt_compress.h),而 zstd-mt_threading.c 为整个 zstdmt 库提供跨平台线程原语包装(详见下文第五节)。
二、通用 Skippable Frame 容器:Lizard / LZ4 / LZ5 / Zstandard 的 12 字节帧头
README 明确给出了这套 MT 容器的核心设计:所有压缩帧都包装成 Zstandard 的 Skippable Frame 格式,魔法数固定为0x184D2A50,每个压缩帧只需额外 12 字节的帧头开销。
通用的 Skippable Frame 定义如下(这是 C/zstdmt/README.md 的原表,完整继承):
| 大小 | 值 | 说明 |
|---|---|---|
| 4 字节 | 0x184D2A50U | Skippable Frame 魔法数(magic) |
| 4 字节 | 4 | Skippable Frame 自身大小(即紧随其后只有 4 字节数据) |
| 4 字节 | compressed size | 其后所跟压缩帧的大小(压缩数据长度) |
也就是说,一个完整的 MT 压缩单元在字节流上是:[4B magic][4B size=4][4B compressed_size][compressed data ...],前 12 字节是容器帧头,其后紧跟某一块输入数据的压缩结果。之所以选择 Skippable Frame 语义,是因为标准的 Zstandard 解码器遇到该 magic 时会把它视为"可跳过帧"直接略过,从而让这份 MT 流在非 MT 感知的工具里也能被容忍、被跳过,不会因未知帧报错。
这一 12 字节布局在源码中有直接印证:LZ4、LZ5、Lizard、Brotli 四个 MT 头文件里都定义了同一个容器魔法数,例如:
- lz4-mt.h:
#define LZ4FMT_MAGICNUMBER 0x184D2204U与#define LZ4FMT_MAGIC_SKIPPABLE 0x184D2A50U - lz5-mt.h:
#define LZ5FMT_MAGIC_SKIPPABLE 0x184D2A50U - lizard-mt.h:
#define LIZARDFMT_MAGIC_SKIPPABLE 0x184D2A50U - brotli-mt.h:
#define BROTLIMT_MAGICNUMBER 0x5242U /* BR */与#define BROTLIMT_MAGIC_SKIPPABLE 0x184D2A50U
所有 MT 变体共用0x184D2A50U作为容器标识,而各算法自己的帧类型魔法数(如 LZ4 的0x184D2204U)仍保留在压缩数据内部,供其原生解码器识别。
三、Brotli 的特有 16 字节帧头:BR 魔法与解压分配提示
Brotli 的封装与通用格式略有不同:由于 Brotli 原生流没有自描述解压后大小的机制,zstdmt 在帧头中额外携带了"解压分配提示",因此帧头从 12 字节扩展为 16 字节。README 中给出的 Brotli 帧定义如下(原表完整继承):
| 大小 | 值 | 说明 |
|---|---|---|
| 4 字节 | 0x184D2A50U | Skippable Frame 魔法数(与 zstd 一致) |
| 4 字节 | 8 | Skippable Frame 大小(此处为 8 字节,而非通用格式的 4) |
| 4 字节 | compressed size | 其后所跟压缩帧的大小(压缩数据长度) |
| 2 字节 | 0x5242U | Brotli 魔法数,即 ASCII 字符"BR" |
| 2 字节 | uncompressed size | 解压器分配提示:64KB × 此值 |
可以这样理解 Brotli 帧的字节布局:前 8 字节是 Skippable Frame 标准头(magic + size=8),后 8 字节是对该帧压缩数据的描述(compressed size +BR标志 + 分配提示)。
在 brotli-mt_compress.c 中可以看到这 16 字节帧头被逐字段写出的真实代码:第 0 字节起写入BROTLIMT_MAGIC_SKIPPABLE,第 4 字节起写入常量8,第 8 字节起写入本次压缩后的实际大小wl->out.size,第 12 字节起写入 2 字节的BROTLIMT_MAGICNUMBER(0x5242),最后第 14 字节起写入 2 字节的分配提示hintsize。其中hintsize的计算逻辑为:若当前输入块实际小于预设inputsize,则hintsize = (in.size >> 16) + 1,否则hintsize = inputsize >> 16——即向上取整到下一个 64KB 边界,保证解压端分配的缓冲区足够容纳整个帧的解压结果。
对应的校验与读取逻辑在 brotli-mt_decompress.c:解压器依次校验size字段必须等于8、第 12 字节起的 2 字节必须等于BROTLIMT_MAGICNUMBER,然后读取第 14 字节起的hintsize,通过hintsize << 16得到该帧解压缓冲区的建议大小,并依据第 8 字节的compressed size动态扩容输入缓冲(不足时realloc),实现"按帧头自描述、逐帧顺序解压"的流程。
四、库的使用方式:上下文结构、回调式 I/O 与错误处理
原 README 将库的用法指向独立的 testutils 与 lib 示例;在本仓库中,库的公开接口全部集中在 C/zstdmt 目录下的各*-mt.h头文件中。下面以 Brotli 为例(其余算法接口完全同构),说明接入这套库的完整调用范式,接口细节来自 brotli-mt.h。
4.1 压缩端 API
/* 1) 创建压缩上下文 */ BROTLIMT_CCtx *BROTLIMT_createCCtx(int threads, uint64_t unpackSize, int level, int inputsize, int lgwin); /* 2) 执行多线程压缩(错误通过返回值检查) */ size_t BROTLIMT_compressCCtx(BROTLIMT_CCtx *ctx, BROTLIMT_RdWr_t *rdwr); /* 3) 获取统计信息 */ size_t BROTLIMT_GetFramesCCtx(BROTLIMT_CCtx *ctx); /* 已生成的帧数 */ size_t BROTLIMT_GetInsizeCCtx(BROTLIMT_CCtx *ctx); /* 输入字节数 */ size_t BROTLIMT_GetOutsizeCCtx(BROTLIMT_CCtx *ctx); /* 输出字节数 */ /* 4) 释放上下文 */ void BROTLIMT_freeCCtx(BROTLIMT_CCtx *ctx);各参数的含义与取值范围(均来自头文件注释与实现校验):
threads:0 .. BROTLIMT_THREAD_MAX(上限 128,定义于 brotli-mt.h)。传0表示不启用容器封装,直接单线程处理原始 brotli 流(输出裸 brotli 格式);传1..128则启用 MT 容器。超过上限会在BROTLIMT_createCCtx内被拒绝并返回0(见 brotli-mt_compress.c)。level:BROTLIMT_LEVEL_MIN(0) .. BROTLIMT_LEVEL_MAX(11),对应 Brotli 压缩级别,超出范围同样拒绝创建。inputsize:每个 worker 每次读取的输入块大小;传0时由库自动选择"该级别下的较优值"——具体实现为1024 * 1024 * (level ? level : 1)(见 brotli-mt_compress.c),即按级别线性放大分块,以平衡压缩率与并行粒度;传非 0 值则完全采用调用者的设置。unpackSize:已知的输入总大小(用于统计/进度),未知可传0。lgwin:Brotli 的滑动窗口大小参数(2 的幂指数),直接透传给BrotliEncoderCompress。
其余算法的常量范围可在对应头文件中确认:LZ4 为LZ4MT_THREAD_MAX=128、LZ4MT_LEVEL_MIN=1、LZ4MT_LEVEL_MAX=12(lz4-mt.h);LZ5、Lizard 结构与之一致。
4.2 解压端 API
BROTLIMT_DCtx *BROTLIMT_createDCtx(int threads, int threadsset, int inputsize); size_t BROTLIMT_decompressDCtx(BROTLIMT_DCtx *ctx, BROTLIMT_RdWr_t *rdwr); size_t BROTLIMT_GetFramesDCtx(BROTLIMT_DCtx *ctx); size_t BROTLIMT_GetInsizeDCtx(BROTLIMT_DCtx *ctx); size_t BROTLIMT_GetOutsizeDCtx(BROTLIMT_DCtx *ctx); void BROTLIMT_freeDCtx(BROTLIMT_DCtx *ctx);解压端threads同样支持0:0表示以单线程方式处理不带 skippable 帧的原始 brotli 流(inputsize参数用于该模式的输入分块);1..128则按 MT 容器解析。值得强调的是 brotli-mt_decompress.c 中实现的自动内容识别:解压器会先把流的开头读入一块prebuf(256+16 字节的预读缓冲),尝试区分"裸 brotli 流"与"MT 容器流"——若流以0x184D2A50+size=8+BR魔法开头则走容器路径,否则按原始 brotli 处理。这样一份由 MT 压缩产生的文件,解压时即使调用方没有显式指定线程数,也能被正确识别并按 MT 方式并行解压。
4.3 回调式读写与 Buffer 结构
库不绑定任何具体 I/O,通过回调注入读写函数(头文件注释明确说明:可使用 stdio 函数或裸 read/write,自行编写包装;7-Zip ZS 中的封装即示例):
typedef struct { void *buf; /* 数据指针 */ size_t size; /* 当前已填充字节数 */ size_t allocated; /* buf 的分配长度 */ } BROTLIMT_Buffer; typedef int (fn_read) (void *args, BROTLIMT_Buffer *in); typedef int (fn_write) (void *args, BROTLIMT_Buffer *out); typedef struct { fn_read *fn_read; /* 读回调 */ void *arg_read; /* 读参数 */ fn_write *fn_write; /* 写回调 */ void *arg_write; /* 写参数 */ } BROTLIMT_RdWr_t;约定:回调返回-1表示出错、返回0表示成功;实际读/写的字节数写入in->size/out->size。
4.4 错误处理
所有 API 的size_t返回值遵循"负数即错误"的约定:MT_ERROR(name)宏定义为((size_t)-BROTLIMT_error_##name),用BROTLIMT_isError(code)判断是否为错误码,用BROTLIMT_getErrorString(code)取可读错误字符串。错误码枚举(brotli-mt.h)覆盖了内存分配失败、读/写失败、畸形输入、数据意外结束、单帧压缩/解压失败、压缩参数越界、底层压缩库错误、操作被取消等场景,对应描述文本在 brotli-mt_common.c 中实现。
五、多线程工作模型:worker 循环与写队列
zstdmt/README.md 只定义了容器格式,而"如何并行"由源码实现决定。以 brotli-mt_compress.c 的头部注释为据,其工作模型是无主线程的纯多 worker 版本:每个线程自行循环执行四步——
- 加读锁(
read_mutex)读取一块输入; - 释放读锁,独立执行本块的压缩(调用
BrotliEncoderCompress,输出缓冲按BrotliEncoderMaxCompressedSize(inputsize) + 16预留帧头空间,见 brotli-mt_compress.c); - 加写锁(
write_mutex)写入结果; - 回到第 1 步,直到输入耗尽。
读锁保证多个 worker 不会读到同一段输入,写锁保证输出顺序不乱。
关键设计是三链表写队列(brotli-mt_compress.c 与 list.h 中的 Linux 风格双向链表实现):
writelist_free:空闲的输出缓冲,可被复用;writelist_busy:worker 正在压缩中的缓冲;writelist_done:压缩完成、等待按序写出的缓冲。
由于并行压缩的各帧完成顺序不定,pt_write()(brotli-mt_compress.c)通过每个writelist上记录的frame序号与上下文中的curframe匹配,保证输出流严格按帧序落盘:只有"当前应写出的帧"就绪时才调用写回调,否则等待;写出一帧后curframe++并继续检查后续帧是否也已就绪(goto again循环),从而在不阻塞并行压缩的前提下维护容器流的帧序不变。解压端(brotli-mt_decompress.c)采用同样的free/busy/done队列模型,只是每帧的解压结果同样按帧号顺序写出。
线程原语方面,threading.h 在 POSIX 系统直接使用pthread.h;在 Windows 上则通过 zstd-mt_threading.c 提供的 Pthread 包装实现兼容:互斥量映射为CRITICAL_SECTION,线程创建用_beginthreadex启动worker中转函数,pthread_join用WaitForSingleObject(handle, INFINITE)等待完成。字节序相关的读写(MEM_readLE32等)与likely/unlikely等宏集中在 memmt.h 中,保证帧头字段以 little-endian 稳定读写。
六、在 7-Zip-zstd 中的集成与验证
zstdmt 并不是孤立库,它已被完整接入本仓库的 7-Zip 构建体系:
- 构建集成:多个构建目标把 C/zstdmt 下的源文件编入。例如 CPP/7zip/Bundles/Alone/makefile.gcc 通过
$(wildcard ../../../../C/zstdmt/lz4*.c)收集 LZ4/LZ5 MT 源码并追加zstd-mt_threading.c;CPP/7zip/7zip.mak 也声明了$(ZSTDMT_OBJS)的目标规则。 - 解压端自动识别:CPP/7zip/Archive/BrotliHandler.cpp 的注释明确说明:只有当用户显式用
-mmt指定线程数时才向解码器强制传线程数;否则BROTLIMT_decompressDCtx()会自行检查流的 skippable-frame 容器魔法,自动决定按裸流单线程解压还是按容器多线程解压(有容器时自动提升到最大处理器数)。因此"用-mmt压缩出的文件,在未加-mmt的解压调用中也能被正确读取"。 - 回归测试:仓库的 tests/regr-arc 目录下保留了
test.txt.br-mt.br等多线程格式的测试样本,配合 tests/main.test 与 tests/7z-test.tcl 可对 MT 容器的压缩/解压与回退行为做端到端验证。
七、注意事项与兼容性边界
基于上述源码事实,使用 zstdmt 时有几点值得注意:
- 线程上限:Brotli/LZ4/LZ5/Lizard 的 MT 库统一以
128为线程数上限,超限请求会在创建上下文阶段被拒绝(返回空指针)。 threads=0的特殊语义:对 Brotli 等库而言,0不是"自动选择",而是"关闭容器、输出裸单线程流"。若想输出标准 MT 容器,必须传1..128。- 帧头开销:通用容器每帧 12 字节、Brotli 每帧 16 字节,分块越小并行度越高但开销占比越大;
inputsize默认按压缩级别自动放大(1MB × level),可视为库在并行度与压缩率之间的默认平衡点,调用者可自行覆盖。 - 解码兼容性:MT 容器复用了 Zstandard 的 Skippable Frame 魔法,标准 zstd 解码器会跳过这些帧;而各算法的原生解码器只能解裸流,要完整还原 MT 容器流,必须使用本库的解压接口或支持该容器的 7-Zip-zstd 构建。
- 许可:zstdmt 库本身以 BSD 协议发布,见 C/zstdmt/LICENSE;7-Zip-zstd 整合后的整体许可见仓库根目录 COPYING。
结语
从字节布局到线程调度,zstdmt 用一个 12/16 字节的 Skippable Frame 容器统一了五种压缩算法的多线程封装:12 字节通用头承载0x184D2A50魔法与压缩尺寸,Brotli 在此基础上追加BR魔法与 64KB 粒度的解压分配提示;每个 worker 通过"读锁取块 → 独立压缩 → 写锁按帧号顺序落盘"的循环,配合free/busy/done三链表队列维持输出帧序。理解这一容器格式,既有助于在 7-Zip-zstd 之外复用 C/zstdmt 的 API 构建自己的多线程压缩工具,也能让你在排查 MT 流问题时,直接按本文的字节表手工解析或构造帧头。
- 桌面应用
- CLI
【免费下载链接】7-Zip-zstd
7-Zip with support for Brotli, Fast-LZMA2, Lizard, LZ4, LZ5 and Zstandard
相关推荐
终极压缩神器!7-Zip-zstd:支持Zstd/Brotli的超级压缩工具全解析 🚀
终极压缩神器!7 Zip zstd:支持Zstd/Brotli的超级压缩工具全解析 🚀 7 Zip zstd是一款基于经典7 Zip开发的增强版开源压缩工具,
桌面应用CLITBOOX/TBOX Zip压缩:ZIP归档格式的多文件压缩
TBOOX/TBOX Zip压缩:ZIP归档格式的多文件压缩 概述 在现代软件开发中,数据压缩是提升存储效率和网络传输性能的关键技术。TBOX作为一个跨平台的C
后端LazyCraft工作流引擎详解:可视化编程如何提升AI开发效率
LazyCraft工作流引擎详解:可视化编程如何提升AI开发效率 LazyCraft是一款功能强大的AI开发工具,其核心工作流引擎通过可视化编程方式,让开发者无
桌面应用CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考