zstd largeNbDicts 基准测试工具详解:大规模字典场景下的解压性能与缓存优化
【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit
导读
本文深入解析 zstd 压缩库随源码一同发布的largeNbDicts基准测试工具(位于 lib/zstd-1.5.7/contrib/largeNbDicts),它是 zstd 官方用于研究"海量字典解压"场景性能的核心工具:当字典数量极大且不断轮换时,字典始终处于"冷"状态,因 CPU 缓存未命中而产生额外延迟。读完本文,你将掌握该工具的全部命令行参数、构建方式、底层基准测试机制(CDict/DDict 预生成、地址乱序、Fastest/Median 聚合),并能用它量化评估--dedicated-dict-search、--dict-content-type、--dict-attach-pref等字典优化手段的实际收益。该工具也随 zstd 以 lib/zstd-1.5.7 子库形式内置于本仓库,其核心 API(如ZSTD_createDDict、ZSTD_decompress_usingDDict)在 src/flb_zstd.c 的 Fluent Bit zstd 压缩/解压封装中也有实际调用,具有直接的工程参考价值。
一、工具定位:为什么需要 largeNbDicts
largeNbDicts是一个专门面向"使用极大量字典进行解压"这一特定场景的基准测试工具(benchmark test tool)。其背景问题在 README.md 中有明确表述:
当字典在不断更换时,它们永远是"冷的"(always "cold"),会因cache misses(缓存未命中)而承受更高的延迟。
与传统"一个字典长期复用"的场景不同,某些业务会维护一个规模巨大的字典池,每条数据使用其中不同的字典解压。此时字典内容无法驻留在 CPU 缓存中,每次解压都需要从主存甚至更低层存储加载字典,缓存未命中成为主要性能瓶颈。该工具正是为此类场景而创建,用于:
- **调查(investigate)**该场景下的真实性能表现;
- **实验(experiment)**各种缓解技术(mitigation techniques),例如 zstd 提供的前缀字典、CDict 表预取、dedicated dict search 等高级选项。
这一目标在源码头部注释中得到印证(largeNbDicts.c):
/* largeNbDicts * This is a benchmark test tool * dedicated to the specific case of dictionary decompression * using a very large nb of dictionaries * thus suffering latency from lots of cache misses. * It's created in a bid to investigate performance and find optimizations. */从仓库结构看,该目录是一个独立可构建的 contrib 工具,仅包含三个文件:README.md(使用文档)、largeNbDicts.c(约 1085 行的完整实现)、Makefile(构建脚本),依赖 zstd 主库(libzstd.a)以及programs/下的通用基准与工具代码。
二、构建方式
largeNbDicts通过 Makefile 构建。其构建依赖关系清晰:
PROGDIR = ../../programs:复用 zstd CLI 程序目录下的util.o、timefn.o、benchfn.o、datagen.o;LIBDIR = ../../lib:编译 zstd 静态库libzstd.a;xxhash.o来自lib/common/xxhash.c。
largeNbDicts: util.o timefn.o benchfn.o datagen.o xxhash.o largeNbDicts.c $(LIBZSTD) $(CC) $(CPPFLAGS) $(CFLAGS) $^ $(LDFLAGS) -o $@$(LIBZSTD)目标会自动执行$(MAKE) -C $(LIBDIR) libzstd.a,因此进入目录后直接执行:
cd lib/zstd-1.5.7/contrib/largeNbDicts make默认CFLAGS ?= -O3(-O3优化级别),并附带-Wall -Wextra等严格告警选项;make clean可清理中间产物与可执行文件。构建成功后即可获得largeNbDicts可执行程序,用法为:
largeNbDicts [Options] filename(s)三、命令行参数全解析
README 给出的完整命令行选项如下,其中每个参数都能在 main() 的参数解析循环 中找到一一对应的实现:
| 参数 | 含义 | 默认值 |
|---|---|---|
-z | 基准测试压缩方向 | 默认开启 |
-d | 基准测试解压方向 | 关 |
-r | 递归加载子目录中的所有文件 | 关 |
-B# | 将输入按#大小切分为块 | 不切分 |
-# | 使用压缩级别# | 3 |
-D # | 使用#文件作为字典 | 自动创建 |
-i# | 基准测试轮数 | 6 |
--nbBlocks=# | 基准测试使用的块数 | 每文件一块 |
--nbDicts=# | 创建#个字典用于测试 | 每块一个字典 |
-h | 显示帮助 | — |
除 README 列出的参数外,源码中实际还支持以下等价长选项与额外开关(来自 largeNbDicts.c 的参数解析逻辑):
--dictionary=<file>:与-D等价;--blockSize=#:与-B等价;--clevel=#:与-#等价;-p#:指标聚合方式,0=fastest(最快)、1=median(中位数),默认0,在usage()帮助文本中也有列出(见 largeNbDicts.c);--prefetch-cdict-tables=#:CDict 表预取开关(取值对应ZSTD_ParamSwitch_e)。
需要注意:-B#与-#中#支持K/M后缀(如-B64K、-B1M),由readU32FromChar()负责解析(largeNbDicts.c)。
典型用法示例:
# 1) 默认:压缩基准,自动为每个输入文件创建 4 KB 字典,压缩级别 3,6 轮 ./largeNbDicts sample1.txt sample2.txt # 2) 解压基准,输入按 64 KB 切块,每块一个字典 ./largeNbDicts -d -B64K sample1.txt # 3) 使用外部字典文件,创建 1000 个字典,测试"大规模冷字典"解压 ./largeNbDicts -d -D mydict.raw --nbDicts=1000 sample1.txt # 4) 递归加载目录全部文件,压缩级别 9,输出每轮中位数 ./largeNbDicts -r -9 -p1 /path/to/dataset关键默认常量(源码级)
实现中定义的默认常量(largeNbDicts.c)帮助理解工具的默认行为:
#define KB *(1<<10) #define MB *(1<<20) #define BLOCKSIZE_DEFAULT 0 /* no slicing into blocks */ #define DICTSIZE (4 KB) /* 自动训练字典的目标大小:4 KB */ #define CLEVEL_DEFAULT 3 /* 默认压缩级别 3 */ #define DICT_LOAD_METHOD ZSTD_dlm_byCopy /* 字典内容以拷贝方式加载 */ #define BENCH_TIME_DEFAULT_S 6 /* 默认 6 轮 */ #define RUN_TIME_DEFAULT_MS 1000 /* 每轮 1000 ms */ #define BENCH_SIZE_MAX (1200 MB) /* 单次加载输入上限 1200 MB */其中DICT_LOAD_METHOD = ZSTD_dlm_byCopy意味着所有字典(无论是自动训练的还是外部加载的)都会以内容拷贝方式装载进 zstd 上下文,这是保证基准可重复性的重要设置。
四、字典的生成与装载:从 ZDICT 训练到 CDict/DDict 池
4.1 字典来源
largeNbDicts支持两种字典来源(createDictionaryBuffer(),largeNbDicts.c):
- 外部字典文件(
-D #/--dictionary=#):直接按二进制文件读入; - 自动训练(默认):调用 zstd 的字典训练 API
ZDICT_trainFromBuffer(),以目标大小DICTSIZE(4 KB)从输入数据块中训练出字典。
若未指定--nbDicts,字典数量默认与块数一致(每块一个字典,见 largeNbDicts.c):
unsigned const nbDicts = nbDictMax ? nbDictMax : nbBlocks;4.2 CDict 与 DDict 池的预生成
为了模拟"海量字典"场景,工具会基于同一份字典数据在内存中批量复制生成N 个压缩字典对象(ZSTD_CDict)与解压字典对象(ZSTD_DDict):
createCDictCollection()循环调用ZSTD_createCDict_advanced2()(largeNbDicts.c),并接受dictContentType与cctxParams(后者承载 dedicated dict search、attach pref 等高级参数);createDDictCollection()循环调用ZSTD_createDDict()(largeNbDicts.c)。
工具还会打印字典池的内存占用估算:压缩侧用ZSTD_sizeof_CDict(),解压侧用ZSTD_estimateDDictSize()(largeNbDicts.c),例如:
generating 1000 dictionaries, using 32.2 MB of memory这一设计直接复刻了真实业务中"内存中维护一个大型字典池、逐个轮换使用"的内存足迹。
4.3 关键技巧:shuffle(地址乱序)
这是本工具模拟真实冷缓存场景的点睛之笔。shuffleCDictionaries()与shuffleDDictionaries()(largeNbDicts.c)会对字典指针数组做两轮随机置换,源码注释明确说明了目的:
/* mess with addresses, so that linear scanning dictionaries != linear address scanning */即:让"按顺序扫描字典下标"与实际"内存地址访问顺序"解耦。这样基准循环中虽然逻辑上按序轮换字典,但每次访问的字典对象都落在不同的内存地址上,从而真实触发缓存未命中,而非因顺序访问获得 CPU 预取带来的虚假提速。
五、基准机制:benchMem 与每轮计时
5.1 被测试的两个核心函数
基准的核心是benchMem(),它把压缩/解压操作包装成统一的benchFn交给BMK_benchTimedFn()(来自programs/benchfn.c)定时执行(largeNbDicts.c)。
压缩路径(compress(),largeNbDicts.c)每次使用ZSTD_CCtx_refCDict()引用下一个 CDict,再调用ZSTD_compress2()完成压缩,随后dictNb递增、到末尾回绕:
ZSTD_CCtx_refCDict(ci->cctx, ci->dictionaries.cdicts[ci->dictNb]); ZSTD_compress2(ci->cctx, dst, srcSize, src, srcSize); ci->dictNb = ci->dictNb + 1; if (ci->dictNb >= ci->nbDicts) ci->dictNb = 0;解压路径(decompress(),largeNbDicts.c)则调用ZSTD_decompress_usingDDict()携带对应的 DDict 解压当前块:
size_t const result = ZSTD_decompress_usingDDict(di->dctx, dst, dstCapacity, src, srcSize, di->dictionaries.ddicts[di->dictNb]);这里体现的ZSTD_decompress_usingDDict/ZSTD_createDDict组合,正是 Fluent Bit 的 zstd 封装在解压侧所用的同一组 API 家族——src/flb_zstd.c 中的zstd_uncompress_unknown_size()同样通过ZSTD_createDCtx()创建上下文、以 64 KB 分块驱动ZSTD_decompressStream()流式解压,并设有 100 MB 的解压上限防护。这从侧面说明:largeNbDicts测得的字典装载开销,会真实传导到 Fluent Bit 这类使用同一 zstd 库的数据管道中。
5.2 计时与指标聚合
- 每轮固定运行
RUN_TIME_DEFAULT_MS(1000 ms),总预算为nbRounds * 1000 ms; - 每轮依据
nanoSecPerRun与sumOfReturn(处理字节数)计算Speed_MBps并实时打印(压缩/解压速度,单位 MB/s); - 全部轮次结束后,
aggregateData()对速度数组排序,按-p选项取Fastest(最快单轮)或Median(中位数)输出(largeNbDicts.c):
Compression Speed : 450.2 MB/s Fastest Speed : 452.1 MB/s5.3 CSV 结果导出
每次基准结束后,工具会把结果追加写入<exeName>.csv(即largeNbDicts.csv),表头为(largeNbDicts.c):
Compression/Decompression,Level,nbDicts,dictAttachPref,metricAggregatePref,Speed每条记录含方向、压缩级别、字典数量、dictAttachPref、聚合方式与最终速度。这意味着你可以通过多组参数连续运行同一工具,把不同优化配置(如不同--dict-attach-pref、不同--nbDicts)的实验结果累积到同一 CSV 中,再用任意数据分析工具绘制对比曲线——这正是该工具作为"性能调查与缓解技术实验平台"的落地形态。
六、高级选项:zstd.h 中的字典优化机制
README 标注这三个高级选项"详见 zstd.h 文档",其底层对应 lib/zstd-1.5.7/lib/zstd.h 中的实验性参数与枚举。main()中通过ZSTD_CCtxParams_setParameter()注入(largeNbDicts.c):
ZSTD_CCtxParams_setParameter(cctxParams, ZSTD_c_enableDedicatedDictSearch, dedicatedDictSearch); ZSTD_CCtxParams_setParameter(cctxParams, ZSTD_c_nbWorkers, 0); ZSTD_CCtxParams_setParameter(cctxParams, ZSTD_c_forceAttachDict, dictAttachPref); ZSTD_CCtxParams_setParameter(cctxParams, ZSTD_c_prefetchCDictTables, prefetchCDictTables);6.1--dedicated-dict-search
对应ZSTD_c_enableDedicatedDictSearch(ZSTD_c_experimentalParam8,见 zstd.h)。开启后,zstd 会为字典建立专门的索引结构,以更大内存占用换取更快的字典匹配搜索。在"字典大、块多"的规模化场景下,这是最常见的调优对象之一,largeNbDicts正是用于量化其收益的工具。
6.2--dict-content-type=#
对应ZSTD_dictContentType_e枚举(zstd.h):
| 值 | 名称 | 含义 |
|---|---|---|
| 0 | ZSTD_dct_auto | 自动判定:以ZSTD_MAGIC_DICTIONARY开头按完整字典处理,否则按原始内容(rawContent)处理 |
| 1 | ZSTD_dct_rawContent | 强制按原始内容加载,即使内容恰好以字典魔数开头 |
| 2 | ZSTD_dct_fullDict | 拒绝加载不符合 zstd 字典规范的输入(必须以字典魔数开头) |
默认ZSTD_dct_auto。注意,ZSTD_dct_rawContent模式本质对应 zstd 的另一项特性——前缀字典(prefix dictionary),即不要求字典是经过训练/格式化的完整字典,任意原始前缀均可参与匹配。
6.3--dict-attach-pref=#
对应ZSTD_dictAttachPref_e枚举(zstd.h),控制压缩时 CDict 与工作上下文(working context)的融合方式,这是影响冷字典延迟的关键参数:
| 值 | 名称 | 行为 |
|---|---|---|
| 0 | ZSTD_dictDefaultAttach | 使用 zstd 内置启发式自动选择 |
| 1 | ZSTD_dictForceAttach | 绝不拷贝字典:原地引用CDict 的表,无启动拷贝开销,但逐字节压缩较慢 |
| 2 | ZSTD_dictForceCopy | 总是把 CDict 内容拷贝进工作上下文:压缩更快,但每次开始时有固定拷贝成本 |
| 3 | ZSTD_dictForceLoad | 总是重新加载字典 |
zstd.h 对三种 CDict 使用方式的代价权衡有精辟论述:拷贝模式在**小输入(< 8 KB)**时初始拷贝成本可能主导总开销,原地引用模式则适合小输入、可复用工作上下文表的场景。在"海量冷字典 + 小数据块"的组合下,dictAttachPref的选择会直接改变每次解压/压缩的固定开销,这正是largeNbDicts的核心实验变量之一(该值也会随结果写入 CSV 便于对照)。
6.4 附加隐藏开关:--prefetch-cdict-tables=#
源码还支持ZSTD_c_prefetchCDictTables(ZSTD_c_experimentalParam16,zstd.h),取值对应ZSTD_ParamSwitch_e(0=auto、1=enable、2=disable),用于在压缩开始前预取 CDict 表到缓存,属于"缓解缓存未命中"方向的直接实验手段。
七、输出解读与实验建议
一次完整的largeNbDicts运行,其 stdout 输出大致为:
loading 4 files... created src buffer of size 128.0 MB split input into 2048 blocks compressing at level 3 without dictionary : Ratio=1.85 (69258000 bytes) compressed using a 4096 bytes dictionary : Ratio=2.91 (43986000 bytes) generating 2048 dictionaries, using 65.5 MB of memory Compression Speed : 412.3 MB/s Fastest Speed : 415.7 MB/s关键信息依次为:加载规模与分块数 →无字典 vs 带字典的压缩比对比(直接量化 4 KB 字典对压缩率的贡献)→ 字典池内存占用 → 各轮速度与聚合速度。
实验设计建议(基于工具机制推断):
- 固定变量:用
-B#控制块大小(对应真实业务中的单条记录大小),用--nbDicts=控制字典池规模,让"块数 × 字典数"可独立调节; - 对照基线:同一数据集分别跑
-d(解压)与-z(压缩),并记录无字典压缩比作为对照; - 参数扫描:对
--dict-content-type=0/1/2、--dict-attach-pref=0/1/2/3、--dedicated-dict-search、--prefetch-cdict-tables=0/1/2做组合实验,结果追加进largeNbDicts.csv后汇总分析; - 稳健性:用
-i提高轮数、-p1采用中位数,降低单轮波动对结论的影响。
八、与 Fluent Bit 项目的关联
本仓库以子库方式内嵌了 zstd 1.5.7(lib/zstd-1.5.7),并在 src/flb_zstd.c 中封装了 Fluent Bit 侧的 zstd 压缩/解压能力:
flb_zstd_compress()以压缩级别 1 调用ZSTD_compress()(src/flb_zstd.c),用于数据压缩通道;flb_zstd_uncompress()/zstd_uncompress_unknown_size()使用ZSTD_DCtx+ZSTD_decompressStream()做流式解压,支持未知输出大小并设 100 MB 上限(src/flb_zstd.c)。
虽然 Fluent Bit 当前封装未直接使用字典模式,但若在 Fluent Bit 插件或自定义输出中引入"字典池式解压"(例如为不同租户/数据源维护各自字典),largeNbDicts所量化的冷字典缓存开销、ZSTD_createDDict/ZSTD_decompress_usingDDict的池化用法以及ZSTD_dictAttachPref_e的取舍,都是直接的参考依据。该工具可作为"引入字典功能前的性能预研"的标准实验手段。
总结
largeNbDicts是 zstd 官方针对"大规模轮换字典 + 缓存未命中"场景设计的精悍基准工具:它通过批量预生成 CDict/DDict 池、地址乱序打散、定时多轮测量与CSV 结果导出,把抽象的"冷字典延迟"转化为可对比的 MB/s 数据,并内置了对 dedicated dict search、dict content type、dict attach pref 等缓解技术的实验支持。无论是评估 zstd 字典特性、设计字典池式解压架构,还是为 Fluent Bit 类数据管道引入字典压缩,该工具都是一把开箱即用的性能标尺。
延伸阅读(仓库内路径)
- 使用文档:lib/zstd-1.5.7/contrib/largeNbDicts/README.md
- 完整实现:lib/zstd-1.5.7/contrib/largeNbDicts/largeNbDicts.c
- 构建脚本:lib/zstd-1.5.7/contrib/largeNbDicts/Makefile
- 字典 API 与枚举定义:lib/zstd-1.5.7/lib/zstd.h
- 字典训练 API:lib/zstd-1.5.7/lib/zdict.h
- 基准计时框架:lib/zstd-1.5.7/programs/benchfn.c
- Fluent Bit 的 zstd 封装:src/flb_zstd.c
【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考