zstd Block-Level Sequence Producer API 实战基于 externalSequenceProducer 编写自定义匹配器与回环测试【免费下载链接】zstdZstandard - Fast real-time compression algorithm项目地址: https://gitcode.com/gh_mirrors/zs/zstdZstandardzstd的 Block-Level Sequence Producer API 允许开发者将块级序列生成即匹配查找 / LZ 解析这一核心环节完全外置由自定义函数接管而 libzstd 负责把生成的序列字面量与匹配后处理为合法压缩块。本文以仓库内的contrib/externalSequenceProducer测试工具为主线完整讲解 API 函数签名与契约、示例 1KB 哈希表 LZ 解析器的实现细节、构建与运行方式并结合 lib/zstd.h 的官方 API 文档与 lib/compress/zstd_compress.c 的底层实现给出替换自定义序列产生器的完整实战方案。一、背景为什么需要块级序列外置zstd 从 v1.5.1 开始提供了帧级 offload APIZSTD_compressSequences()用户一次性提交整帧的序列由 libzstd 完成后续编码。而 Block-Level Sequence Producer API 是更细粒度的块级方案每个压缩块都会回调用户注册的ZSTD_sequenceProducer_F函数来生成序列。两者的关键差异在于迁移成本帧级 API 要求应用改造压缩调用方式把喂数据给 zstd改成产出序列交给 zstd块级 API 下应用继续调用原有的ZSTD_compress2()或ZSTD_compressStream2()即可只是内部解析逻辑被替换为用户实现从而透明地获得自定义匹配器的收益例如针对输入特征调优、获得更好的速度/压缩比或利用 libzstd 内部没有的硬件加速参见 lib/zstd.h 的 OVERVIEW 一节。本仓库 contrib/externalSequenceProducer/README.md 明确指出externalSequenceProducer正是这一 API 的测试工具演示如何利用它完成一次完整的往返round-trip压缩解压测试其中示例序列产生器实现了基于 1KB 哈希表的 LZ 解析字典解析目前不受支持。二、API 核心函数签名与参数契约实现一个外部序列产生器本质上是实现一个ZSTD_sequenceProducer_F类型的函数lib/zstd.htypedef size_t (*ZSTD_sequenceProducer_F) ( void* sequenceProducerState, /* 用户自管状态指针 */ ZSTD_Sequence* outSeqs, /* 输出序列缓冲区 */ size_t outSeqsCapacity, /* 缓冲区容量 */ const void* src, size_t srcSize, /* 待解析的输入块 */ const void* dict, size_t dictSize,/* 历史缓冲区当前恒为空 */ int compressionLevel, /* 当前压缩级别 */ size_t windowSize /* 外部序列允许的最大偏移 */ );zstd 对每次回调传入的参数有明确保证lib/zstd.houtSeqsCapacity保证 ZSTD_sequenceBound(srcSize)即缓冲区容量一定足够装下该块的所有可能序列该内存由 CCtx 管理。srcSize保证 ZSTD_BLOCKSIZE_MAX默认 128KB即每次回调面对的是一个完整块。dict / dictSize历史缓冲区可能为空。当前 zstd 总是传入dictSize 0详见后文限制一节因此实现中不必依赖该参数。compressionLevel用户设置的压缩级别可用于调整策略与速度/压缩比权衡注意它不反映通过高级 API 设置的其它参数。windowSize外部序列允许的最大偏移序列的 offset 字段必须遵守这一限制存在字典时可能例外见 doc/zstd_compression_format.md。返回值是写入outSeqs的序列数量同时承担错误报告职责返回值 outSeqsCapacity一律视为错误码ZSTD_SEQUENCE_PRODUCER_ERROR值为(size_t)(-1)宏专为此提供lib/zstd.h若srcSize非零返回值必须非零一旦返回错误zstd 会按ZSTD_c_enableSeqProducerFallback的设定要么回退到内部序列产生器要么直接让本次压缩失败。2.1 序列结构 ZSTD_Sequence产出序列使用ZSTD_Sequence结构lib/zstd.htypedef struct { unsigned int offset; /* 匹配的偏移非 offset codeoffset0 且 matchLength0 时 * 表示块尾由 litLength 个字面量收尾 */ unsigned int litLength; /* 字面量长度 */ unsigned int matchLength; /* 匹配长度 */ unsigned int rep; /* 重复偏移指示范围 [0,3]外部序列产生器可不计算该字段 */ } ZSTD_Sequence;其中rep字段对ZSTD_generateSequences()有意义但从外部序列产生器视角重复偏移并非必须计算——例如ZSTD_compressSequences()目前完全不使用rep字段。2.2 何为合法 parse正确性契约zstd 只有在ZSTD_c_validateSequencesZSTD_c_experimentalParam12见 lib/zstd.h启用时才会校验序列合法性并在不合法时让压缩失败否则不合法的 parse 可能直接导致数据损坏。合法 parse 必须满足lib/zstd.h所有序列的matchLength与litLength之和必须等于srcSize即完整覆盖输入块除最后一个序列外所有序列matchLength ZSTD_MINMATCH_MIN最小匹配长度常量最后一个序列的matchLength要么 ZSTD_MINMATCH_MIN要么为 0所有 offset 必须遵守windowSize限制若最后一个序列matchLength 0则其offset也必须为 0即纯字面量收尾。三、示例程序逐段解读main.c 的完整往返测试contrib/externalSequenceProducer/main.c 演示了注册外部序列产生器、执行压缩解压并校验数据一致性的完整流程。3.1 注册序列产生器关键一步ZSTD_CCtx* const zc ZSTD_createCCtx(); int simpleSequenceProducerState 0xdeadbeef; // Here is the crucial bit of code! ZSTD_registerSequenceProducer( zc, simpleSequenceProducerState, simpleSequenceProducer );ZSTD_registerSequenceProducer()lib/zstd.h把序列产生器状态指针与函数绑定到 CCtxsequenceProducerState必须由调用方先初始化其生命周期也由调用方负责示例中是一个0xdeadbeef哨兵值该设置是粘性的跨多次压缩持续有效直到下一次参数重置传入NULL函数指针即可清除并解除后续所有限制注册属于高级参数因此只有尊重高级参数的压缩 API如ZSTD_compress2()、ZSTD_compressStream2()才生效早于高级参数体系引入的ZSTD_compressCCtx()等旧 API 会忽略该设置。从底层实现看该函数只是把状态与函数指针写入zc-requestedParamslib/compress/zstd_compress.c本质是参数登记真正调用发生在每个块的压缩路径中。3.2 启用回退参数size_t const res ZSTD_CCtx_setParameter(zc, ZSTD_c_enableSeqProducerFallback, 1); CHECK(res);ZSTD_c_enableSeqProducerFallbackZSTD_c_experimentalParam17默认值为 0允许值 0/1见 lib/zstd.h控制当外部序列产生器返回错误码时的行为置 1逐块回退——只有外部产生器返回错误的那些块改由内部序列产生器处理其余块仍走自定义实现回退解析遵循其它 cParam 设置如压缩级别。置 0任何错误直接让压缩失败。示例将其设为 1保证即便外部解析器出现意外也能完成压缩适合作为测试工具的稳妥配置。3.3 读取输入并压缩程序用fseek/ftell探测文件大小整体读入src然后size_t const dstSize ZSTD_compressBound(srcSize); char* const dst malloc(dstSize); size_t const cSize ZSTD_compress2(zc, dst, dstSize, src, srcSize); CHECK(cSize);ZSTD_compressBound()给出最坏情况下的压缩输出上界ZSTD_compress2()是尊重高级参数的单发压缩 API——正是外部序列产生器能够生效的入口。3.4 解压并校验一致性size_t const res ZSTD_decompress(val, srcSize, dst, cSize); CHECK(res); if (memcmp(src, val, srcSize) 0) { printf(Compression and decompression were successful!\n); printf(Original size: %lu\n, srcSize); printf(Compressed size: %lu\n, cSize); } else { printf(ERROR: input and validation buffers dont match!\n); ... }往返测试的关键点在于解压结果必须与原始输入逐字节一致。若不一致程序会定位并打印第一个坏索引First bad index并返回非零退出码——这正是校验外部序列产生器输出的合法 parse性质最直接的验证手段。由于序列的 offset/litLen/matchLen 必须精确覆盖输入任何解析错误都会在此处暴露。四、示例序列产生器源码剖析1KB 哈希表的 LZ 解析contrib/externalSequenceProducer/sequence_producer.c 提供了一个约 80 行的完整参考实现其核心设计如下4.1 参数与常量#define HSIZE 1024 static U32 const HLOG 10; /* 哈希表大小 2^10 1024 */ static U32 const MLS 4; /* 参与哈希的字节数 */ static U32 const BADIDX 0xffffffff; /* 空槽位标记 */HSIZE 1024对应 README 所称1KB hashtable1024 个U32索引项MLS 4表示用 4 字节计算哈希与 zstd 内部常见的最小匹配策略一致BADIDX表示哈希槽中尚无匹配索引。4.2 主循环哈希 匹配查找while (ip MLS iend) { size_t const hash ZSTD_hashPtr(ip, HLOG, MLS); U32 const matchIndex hashTable[hash]; hashTable[hash] (U32)(ip - istart); if (matchIndex ! BADIDX) { const BYTE* const match istart matchIndex; U32 const matchLen (U32)ZSTD_count(ip, match, iend); if (matchLen ZSTD_MINMATCH_MIN) { U32 const litLen (U32)(ip - anchor); U32 const offset (U32)(ip - match); ZSTD_Sequence const seq { offset, litLen, matchLen, 0 }; /* Note: its crucial to stay within the window size! */ if (offset windowSize) { outSeqs[seqCount] seq; ip matchLen; anchor ip; continue; } } } ip; }实现逻辑简洁清晰对当前位置 4 字节计算哈希查表得到上次出现该哈希的位置matchIndex再写入当前位置若存在候选匹配用ZSTD_count()数出实际匹配长度内部实现并要求 ZSTD_MINMATCH_MIN才接受构建一条ZSTD_Sequenceoffset ip - match当前偏移、litLen ip - anchor锚点累积的字面量、matchLen、rep 0关键约束只有当offset windowSize时才采纳该序列——代码注释明确强调crucial to stay within the window size这是对 API 契约第 3 条的直接遵守因为解码端窗口放不下过大的偏移会导致压缩结果无法解码采纳序列后ip直接跳过匹配anchor前移否则ip继续扫描。4.3 收尾序列剩余字面量{ ZSTD_Sequence const finalSeq { 0, (U32)(iend - anchor), 0, 0 }; outSeqs[seqCount] finalSeq; }循环结束后以offset 0, matchLength 0的最终序列承接从anchor到块尾的所有字面量——完全符合最后一个序列matchLength 0时offset必须为 0的契约第 4 条也保证了各段长度之和恰等于srcSize。4.4 未使用的入参函数对sequenceProducerState、dict、dictSize、outSeqsCapacity、compressionLevel显式(void)丢弃。结合 README 的说明基于字典的解析目前不受支持——这与 API 现状zstd 总是传入dictSize 0一致示例实现因此无需字典逻辑。对应的 sequence_producer.h 通过#define ZSTD_STATIC_LINKING_ONLY引入zstd.h并声明simpleSequenceProducersize_t simpleSequenceProducer(void*, ZSTD_Sequence*, size_t, const void*, size_t, const void*, size_t, int, size_t)与ZSTD_sequenceProducer_F签名完全吻合是自定义实现时可直接复制的模板。五、构建与运行5.1 编译contrib/externalSequenceProducer/Makefile 负责构建make -C contrib/externalSequenceProducer要点说明目标externalSequenceProducer由sequence_producer.c、main.c与$(LIBDIR)/libzstd.a共同链接CPPFLAGS显式包含-I$(LIBDIR) -I$(LIBDIR)/compress -I$(LIBDIR)/common——因为sequence_producer.c直接#include zstd_compress_internal.h需要访问压缩内部头文件$(LIBZSTD)目标会先进入 lib/Makefile 用make libzstd.a CFLAGS$(CFLAGS)构建静态库编译开启-stdgnu99以及一长串严格告警-Wall -Wextra -Wcast-qual -Wcast-align -Wshadow ...保证示例代码自身的健壮性。5.2 运行按 README 与 main.c 的用法提示传入一个文件路径./externalSequenceProducer file程序行为若参数个数不是 2打印Usage: externalSequenceProducer file并返回 1压缩、解压成功后打印Compression and decompression were successful!、Original size: N、Compressed size: M数据不一致时打印首个坏索引并返回 1可用echo $?检查退出码任何 zstd API 返回错误时通过CHECK宏打印ERROR: 错误名ZSTD_getErrorName并退出。六、配套参数与静态上下文用法6.1 ZSTD_c_validateSequences该 cParam默认 0启用后zstd 会在压缩前校验外部序列产生器产出的序列是否满足 2.2 节的合法性条件不合法则压缩失败。它的存在让调试自定义匹配器变得更安全——代价是额外的性能开销lib/zstd.h。在开发期建议开启上线前按需关闭。6.2 ZSTD_CCtxParams_registerSequenceProducerZSTDLIB_STATIC_API void ZSTD_CCtxParams_registerSequenceProducer( ZSTD_CCtx_params* params, void* sequenceProducerState, ZSTD_sequenceProducer_F sequenceProducer );该函数与ZSTD_registerSequenceProducer()等价但操作对象是ZSTD_CCtx_params用于配合ZSTD_estimateCCtxSize_usingCCtxParams()与ZSTD_initStaticCCtx()做静态内存 CCtx场景下的精确尺寸估算lib/zstd.h。仓库 tests/zstreamtest.c 中提供了该函数的具体用法示例可作为参考。七、使用限制与注意事项官方 API 文档明确列出三条当前限制lib/zstd.h使用前必须了解不支持长距离匹配LDM启用ZSTD_c_enableLongDistanceMatching时使用块级外部序列产生器压缩会直接失败。该参数在某些场景会被自动启用例如当前ZSTD_c_windowLog 128MB时默认禁用但该行为可能变化注册外部序列产生器时需在相关高级设置场景下显式检查并ZSTD_ps_disable之。不支持历史缓冲区zstd 当前总是向外部序列产生器传入dictSize 0。由此产生两个推论字典当前不生效引用字典不会让压缩失败但字典内容被忽略流式历史不支持——所有高级压缩 API含流式 API都能配合外部序列产生器工作但每个块被当作独立块解析不携带前序块的历史。单次压缩内不支持多线程ZSTD_c_nbWorkers 0时压缩会失败。跨压缩并行不受影响——每个线程创建各自的 CCtx 即可如 tests/zstreamtest.c 中的并发用法。文档同时说明这三条限制并非技术死结只是当前工程实现的范围取舍长期计划中均会解除。工程实践中若你的场景同时需要多线程或字典可评估每线程独立 CCtx 各自注册序列产生器的变通方案。八、替换为自己的序列产生器参照示例替换成本极低只需三步复制 sequence_producer.h 的函数签名实现自己的size_t myProducer(void* state, ZSTD_Sequence* outSeqs, size_t outSeqsCapacity, const void* src, size_t srcSize, const void* dict, size_t dictSize, int compressionLevel, size_t windowSize)解析时始终遵守 2.2 节的四条合法性契约特别是offset windowSize与终止序列的offset 0约定可在开发期开启ZSTD_c_validateSequences辅助校验在 main 流程中用ZSTD_registerSequenceProducer(zc, myState, myProducer)替换示例注册调用并视需要设置ZSTD_c_enableSeqProducerFallback。构建时只需把 Makefile 中externalSequenceProducer: sequence_producer.c main.c $(LIBZSTD)的源文件列表替换为你的实现文件即可头文件包含路径-I$(LIBDIR)/compress -I$(LIBDIR)/common保持不变——若你的实现只用公共 API甚至可以去掉这两个内部目录包含。九、总结contrib/externalSequenceProducer是理解 zstd Block-Level Sequence Producer API 的最小但完整的参考实现main.c展示了注册、回退参数、压缩解压与一致性校验的完整调用链sequence_producer.c用 80 行代码演示了 1KB 哈希表 LZ 解析器如何严格满足 API 的合法性契约lib/zstd.h 则给出了函数签名、参数语义、错误处理与三条限制的权威说明。对于需要在 zstd 之上接入自定义匹配器、专用解析逻辑或硬件加速的开发者这套组合文档 示例 测试提供了从理解契约到落地替换的完整路径。【免费下载链接】zstdZstandard - Fast real-time compression algorithm项目地址: https://gitcode.com/gh_mirrors/zs/zstd创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考