尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MongoDB 仓库内嵌 zstd 的 zlib 兼容层:zlibWrapper 快速接入与性能调优实战指南

MongoDB 仓库内嵌 zstd 的 zlib 兼容层:zlibWrapper 快速接入与性能调优实战指南 MongoDB 仓库内嵌 zstd 的 zlib 兼容层zlibWrapper 快速接入与性能调优实战指南【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo导读zlibWrapper 是 zstd 官方为 zlib 用户提供的一层无缝兼容包装你不需要重写任何压缩调用只需把#include zlib.h换成#include zstd_zlibwrapper.h再把链接目标追加 zstd 库就能让既有 zlib 项目直接获得 Zstandard 的压缩能力。在 MongoDB 仓库中这一实现随 zstd 第三方源码一同携带位于 src/third_party/zstandard/zstd/zlibWrapper 目录。读完本文你将掌握zlibWrapper 的构建所需文件、嵌入既有工程的三种方式、运行时与编译期启用 zstd 压缩的方法、自动识别 zlib/zstd 流的解压原理以及借助zwrapbench和上下文复用等技巧进行性能基准与调优的完整方案。zlibWrapper 的设计目标与工作方式为什么需要一层 zlib 包装zstd 团队创建 zlibWrapper 的初衷是让已经在使用 zlib 的项目能够快速、平滑地迁移到 Zstandard见 README。其核心思路不是改造调用方而是在 zlib API 之上做同名替换应用层仍然调用deflateInit/deflate/inflate等标准 zlib 接口包装层根据开关状态把这些调用透明地转发给 zstd 的流式接口ZSTD_CStream/ZSTD_DStream解压侧则在读取流头部的 4 字节魔数后自动判别是 zlib 流还是 zstd 帧从而选择正确的解码器。这意味着零业务代码改动即可让老项目用上新压缩算法同时保留了在 zlib 与 zstd 之间随时切换的能力。构建所需的文件清单README 明确列出了构建该包装层所必需的输入文件文件来源zlib.h系统/第三方 zlib 发行包不随 zstd 提供静态或动态 zlib 库libz.a/libz.so/libz.dll同上所有 zlib 项目原本就依赖zstd_zlibwrapper.h/zstd_zlibwrapper.czstd 发行包内置gzclose.c、gzlib.c、gzread.c、gzwrite.czstd 发行包内置gz* 兼容实现gzcompatibility.h、gzguts.hzstd 发行包内置静态或动态 zstd 库libzstd.a/libzstd.sozstd 发行包内置其中前两项是所有 zlib 项目本来就需要的其余文件均随 zstd 发行包提供。在 MongoDB 仓库中这些文件全部位于 src/third_party/zstandard/zstd/zlibWrapper含 gzclose.c、gzlib.c、gzread.c、gzwrite.c 以及头文件 gzcompatibility.h、gzguts.h。从源码看包装层的头文件 zstd_zlibwrapper.h 在包含zlib.h之前定义了ZLIB_CONST、Z_PREFIX与ZLIB_INTERNALZ_PREFIX让 zlib 的所有导出符号带上z_前缀从而避免与原生 zlib 符号冲突ZLIB_INTERNAL用于禁用 gz*64 系列函数以修复旧版本 zlib 1.2.4 在Z_PREFIX下的兼容问题。同时它对外暴露zstdVersion()、ZWRAP_useZSTDcompression()、ZWRAP_setPledgedSrcSize()等扩展接口这是原生 zlib 头文件所没有的。将 zlibWrapper 嵌入你的项目经典三步接入法README 以项目原本使用gcc project.o -lz编译为起点给出了完整的接入流程把源码中所有#include zlib.h替换为#include zstd_zlibwrapper.h编译时额外加入zstd_zlibwrapper.c、gz*.c与静态/动态 zstd 库将链接命令改为gcc project.o zstd_zlibwrapper.o gz*.c -lz -lzstd即保留-lzzlib 库新增-lzstd并把包装层与 gz* 兼容实现一并链接进最终产物。Makefile 层面的参考实现仓库自带的 zlibWrapper/Makefile 给出了更完整的构建配方可直接作为集成模板ZLIB_LIBRARY ? -lz # 可通过 ZLIB_LIBRARY 覆盖为具体库路径 ZLIB_PATH ? . # 通过 ZLIB_PATH 指定 zlib.h 所在目录 ZSTDLIBDIR ../lib # zstd 静态库所在目录 ZSTDLIBRARY $(ZSTDLIBDIR)/libzstd.a GZFILES gzclose.o gzlib.o gzread.o gzwrite.o该 Makefile 的关键设计是一份源码、两个编译变体example/fitblk/minigzip链接zstd_zlibwrapper.ozstd 压缩默认关闭example_zstd/fitblk_zstd/minigzip_zstd链接zstdTurnedOn_zlibwrapper.o该目标通过CPPFLAGS -DZWRAP_USE_ZSTD1编译 zstd_zlibwrapper.c对应 Makefile 中zstdTurnedOn_zlibwrapper.o规则从而在编译期就启用 zstd 压缩。此外make test目标依次运行 example、fitblk10240/40960 字节两种块大小、minigzip 的 gzip 压缩/解压往返以及zwrapbench基准make test-valgrind则用 valgrind 检查内存问题。需要指定 zlib 库位置时使用make ZLIB_PATH/path/to/zlib ZLIB_LIBRARY/path/to/libz.so。启用 zstd 压缩编译期与运行期两条路径默认行为保持 zlib 语义嵌入包装层后zstd 压缩默认是关闭的——这是刻意的安全设计项目在未做任何行为改变的情况下继续像以前一样用 zlib 工作。这一默认值可以在 zstd_zlibwrapper.c 中看到#ifndef ZWRAP_USE_ZSTD #define ZWRAP_USE_ZSTD 0 #endif随后全局开关g_ZWRAP_useZSTDcompression以此值初始化对应源码static int g_ZWRAP_useZSTDcompression ZWRAP_USE_ZSTD;。包装层中每个z_*接口如z_deflateInit_、z_deflate、z_deflateEnd的入口处都会先检查该开关关闭时直接透传给原生 zlib 实现打开时才走 zstd 路径。两种启用方式README 给出了两条启用路径方式一编译期推荐可静态保证gcc project.o zstd_zlibwrapper.o gz*.c -DZWRAP_USE_ZSTD1 -lz -lzstd或者在#include zstd_zlibwrapper.h之前于源码中写入#define ZWRAP_USE_ZSTD 1 #include zstd_zlibwrapper.h方式二运行期调用头文件中声明的运行开关void ZWRAP_useZSTDcompression(int turn_on); // 1 开启 / 0 关闭 int ZWRAP_isUsingZSTDcompression(void); // 查询当前状态头文件 zstd_zlibwrapper.h 明确提醒该运行开关是进程全局变量且非线程安全ZWRAP_useZSTDcompression()并发调用可能产生竞争条件应在多线程启动之前设置。这一点在源码中体现得也很直白——开关就是一个全局 int开启后对所有线程的所有流生效。解压侧自动识别 zlib / zstd 流与压缩侧不同解压侧默认就是自动模式包装层会检查输入流的魔数zstd 帧的ZSTD_MAGICNUMBER自动判断当前流是 zlib 压缩还是 zstd 压缩并选择对应的解码器。因此即使压缩端开启了 zstd历史上用 zlib 写出的旧数据也能被正常解压反之亦然。其实现位于 zstd_zlibwrapper.cz_inflate在读取前 4 字节ZLIB_HEADERSIZE时通过ZWRAP_readLE32()与ZSTD_MAGICNUMBER比较匹配则走ZSTD_DStream解压路径不匹配则把缓冲的头部交还给原生inflate继续按 zlib 处理并通过strm-reserved字段标记流类型ZWRAP_ZLIB_STREAM/ZWRAP_ZSTD_STREAM/ZWRAP_UNKNOWN_STREAM。如果明确知道所有输入都是 zlib 数据可调用ZWRAP_setDecompressionType(ZWRAP_FORCE_ZLIB);强制 zlib 解压——README 指出这会让 zlib 流的解压速度略有提升省去魔数探测与包装层开销。解压类型同样由全局变量控制g_ZWRAPdecompressionType默认ZWRAP_AUTO可通过ZWRAP_getDecompressionType()查询且同样非线程安全。实战验证example.c 一例两跑官方示例的运行输出zlibWrapper 目录下的 examples/example.c 直接取自 zlib 官方发行包的test/example.c仅做了两处最小改动文件头部注释中明确说明将#include zlib.h改为#include zstd_zlibwrapper.h在 zstd 压缩开启时禁用test_flush与test_sync两个测试函数——它们依赖Z_FULL_FLUSH与inflateSync而这两项在包装层中暂不支持。纯 zlib 编译运行zstd 关闭的输出zlib version 1.2.8 0x1280, compile flags 0x65 uncompress(): hello, hello! gzread(): hello, hello! gzgets() after gzseek: hello! inflate(): hello, hello! large_inflate(): OK after inflateSync(): hello, hello! inflate with dictionary: hello, hello!开启 zstd 编译运行-DZWRAP_USE_ZSTD1且追加链接zstd_zlibwrapper.o gz*.c -lzstd的输出zlib version 1.2.8 0x1280, compile flags 0x65 uncompress(): hello, hello! gzread(): hello, hello! gzgets() after gzseek: hello! inflate(): hello, hello! large_inflate(): OK inflate with dictionary: hello, hello!两条输出几乎完全一致只少了after inflateSync()一行——这正是无缝切换的最直观证明compress/uncompress、gz 文件读写、deflate/inflate 小缓冲流、大块数据、字典压缩这些 zlib 核心路径在 zstd 后端下行为完全一致。注意main中通过ZWRAP_isUsingZSTDcompression()判断后才调用test_flush/test_sync对应 example.c并在开启 zstd 时额外打印一行zstd version ...通过zstdVersion()获得。从源码看字典与压缩级别的映射示例中字典测试之所以能通过是因为包装层把deflateSetDictionary映射到了 zstd 的ZSTD_CCtx_loadDictionary见 zstd_zlibwrapper.c把inflateSetDictionary映射到ZSTD_DCtx_loadDictionary而Z_DEFAULT_COMPRESSION会被换算成 zstd 的默认压缩级别 3常量ZWRAP_DEFAULT_CLEVEL 3见 zstd_zlibwrapper.c。也就是说应用层无需感知 zlib 与 zstd 在压缩级别语义上的差异包装层负责翻译。性能基准用 zwrapbench 量化 zstd 收益工具用法zstd 发行包附带了一个专用于该包装层的基准工具zwrapbench可以同时测量 zlib、zstd 以及包装层三者的压缩速度、解压速度与压缩比。其源码位于 examples/zwrapbench.c用法签名与核心参数如下zwrapbench [args] [FILE(s)] [-o file]-b#以 # 作为基准压缩级别默认 3-e#测试从-bX到 # 的连续压缩级别区间-i#每个级别的最短测试时间秒默认 3 秒-B#把大文件切分为 # 大小的独立压缩块-r递归处理目录参数多个文件名、通配符均可作为参数传入。基准的数据处理方式是先把文件整体读入内存再独立处理README 明确指出从而消除了 I/O 开销使结果更能反映压缩算法本身的真实性能。不提供文件名时使用合成数据。编译方式即make zwrapbench对应 Makefile 中zwrapbench: zwrapbench.o zstd_zlibwrapper.o util.o timefn.o datagen.o $(ZSTDLIBRARY)规则。Makefile 的test目标里也有现成的基准命令示例./zwrapbench -qi1b3B1K ../doc/zstd_compression_format.md ./zwrapbench -rqi1b1e3 ../lib-q抑制警告-i1表示每个级别至少测 1 秒。README 中的实测数据复现环境README 记录的实验使用zwrapbench -ri6b6zlib 与 zstd 均为压缩级别 6输入数据为包含 2979 个文件的解压后 git 仓库来自 git/git master.zip 归档。关键测量数据如下压缩方式压缩速度解压速度压缩后大小压缩比zlib 1.2.8原生不复用上下文30.22 MB/s218.1 MB/s68197833.459zlib 1.2.8zlibWrapper复用上下文30.40 MB/s218.9 MB/s68197833.459zlib 1.2.8zlibWrapper不复用上下文30.28 MB/s218.1 MB/s68197833.459zstd 1.1.0ZSTD_CCtx68.35 MB/s430.9 MB/s68685213.435zstd 1.1.0ZSTD_CStream66.63 MB/s422.3 MB/s68685213.435zstd 1.1.0zlibWrapper复用上下文54.01 MB/s403.2 MB/s67634823.488zstd 1.1.0zlibWrapper不复用上下文51.59 MB/s383.7 MB/s67634823.488从这张表可以读出三个结论数据为 README 记录的特定环境结果实际数值会随 zlib/zstd 版本与硬件变化zstd 明显快于 zlib原生 zstd 压缩约 2.2 倍、解压约 2 倍于 zlib包装层有少量开销zstd 走 zlibWrapper 后性能从约 66~68 MB/s 降到约 51~54 MB/s压缩但仍显著快于 zlib复用上下文带来收益在该实验中zlibWrapper 复用上下文比不复用压缩快约 4%、解压快约 5%而压缩比3.488还略优于 zlib 的 3.459。zwrapbench的-B分块参数与这一复用机制配合可以在流式压缩场景中精确控制内存与块粒度README 的 Makefile 测试中-B1K、-B10240、-B40960即此类用法。提升流式压缩性能pledged source size问题背景流式压缩时压缩器事先不知道待压缩数据的总大小。zstd 的默认假设是数据大于 256 KB但对于小于 256 KB 的数据这个默认假设会拖累压缩速度与压缩比——因为压缩器无法为窗口、哈希表等参数做最有利的配置。解决方案ZWRAP_setPledgedSrcSize()包装层为此提供了ZWRAP_setPledgedSrcSize()接口int ZWRAP_setPledgedSrcSize(z_streamp strm, unsigned long long pledgedSrcSize);作用为给定的压缩流预先声明源数据大小pledged source size从而改变 zstd 的压缩参数窗口大小、链长、哈希表等可能同时改善压缩速度与压缩比调用时机必须在deflateInit()或deflateReset()之后、deflate()或deflateSetDictionary()之前调用适用场景仅当数据分块压缩时有效若deflateInit()/deflateReset()后立刻调用deflate(strm, Z_FINISH)一次性压缩完该情况会被自动检测调用不会产生任何改变。其底层实现在 zstd_zlibwrapper.c把声明值存入zwc-pledgedSrcSize并把压缩状态置为ZWRAP_useInit随后在ZWRAP_initializeCStream()中通过ZSTD_getParams(level, pledgedSrcSize, dictSize)生成对应的压缩参数并应用到ZSTD_CCtx见 zstd_zlibwrapper.cZSTD_CCtx_setPledgedSrcSize再把声明值传给 zstd。从源码可推断声明值会在每次deflateReset后的首轮压缩中被重新应用从而保证复用上下文时每个流都能获得正确的参数。配套接口保留字典的 Reset头文件中还提供了两个与上下文/字典复用配套的扩展int ZWRAP_deflateReset_keepDict(z_streamp strm); // 保留 deflateSetDictionary 设置的字典 int ZWRAP_inflateReset_keepDict(z_streamp strm); // 保留 inflateSetDictionary 设置的字典在 zstd 模式下ZWRAP_deflateReset_keepDict只重置流状态而不清除字典zstd_zlibwrapper.c从而减少重复deflateSetDictionary的次数在 zlib 模式下两者直接转发给deflateReset/inflateReset。解压侧inflate()只会在首次遇到需要字典时返回一次Z_NEED_DICT从而提升解压速度。复用压缩上下文把小流压缩提速 4%~5%问题背景多次独立压缩的开销普通 zlib 编程模式下压缩两个文件/流会分别分配两个上下文文件 1deflateInit → deflate → ... → deflate → deflateEnd 文件 2deflateInit → deflate → ... → deflate → deflateEnd每次deflateInit都要重新分配并初始化整套内部状态窗口、哈希表、参数对小文件/小流而言这笔初始化开销占比可观。复用模式README 给出了标准的上下文复用序列deflateInit # 初始化一次上下文 文件 1deflate → ... → deflate 文件 2deflateReset → deflate → ... → deflate deflateEnd # 最后统一释放即中间每处理一个新流用deflateReset而非deflateInit复用已有上下文。README 的实验同一份 2979 文件数据、级别 6显示复用上下文对 zlib 影响甚微但对 zstd 有明显提升——zlibWrapper zstd 复用比不复用压缩快约 4%、解压快约 5%51.59 → 54.01 MB/s383.7 → 403.2 MB/s。从源码机制看z_deflateReset在 zstd 模式下通过ZSTD_CCtx_reset(zbc, ZSTD_reset_session_only)仅重置会话状态、保留已分配的缓冲区与参数配置zstd_zlibwrapper.c这正是复用能省下重复分配与初始化开销的根本原因而 zlib 本身的deflateReset本就设计为可复用因此两者差异不大。类似地解压侧z_inflateReset会复用ZSTD_DStream配合ZWRAP_inflateReset_keepDict还能在复用同时保留字典。兼容性边界支持、忽略与不支持的方法清单启用 zstd 压缩后并非所有原生 zlib 函数都可用。README 明确调用不支持的方法时包装层会把错误消息写入strm-msg并返回Z_STREAM_ERROR。受支持的方法deflateInit、deflate例外Z_FULL_FLUSH、Z_BLOCK、Z_TREES三种 flush 模式不支持、deflateSetDictionary、deflateEnd、deflateReset、deflateBoundinflateInit、inflate、inflateSetDictionary、inflateReset、inflateReset2compress、compress2、compressBound、uncompressgzip 文件访问函数族gz*其中z_compress/z_compress2在 zstd 模式下直接调用ZSTD_compresszstd_zlibwrapper.cz_uncompress则先通过ZSTD_isFrame()探测输入是否为 zstd 帧再决定走ZSTD_decompress还是原生uncompress对应 zstd_zlibwrapper.cz_deflateBound/z_compressBound在 zstd 模式映射为ZSTD_compressBound。被忽略的方法静默无操作deflateParamszstd 模式下直接返回Z_OK不做任何事zstd_zlibwrapper.c。这是因为 zstd 没有与 zlib 完全对等的运行中动态调整 level/strategy语义调用被安全地忽略。不支持的方法返回 Z_STREAM_ERROR压缩侧deflateCopy、deflateTune、deflatePending、deflatePrime、deflateSetHeader 解压侧inflateGetDictionary、inflateCopy、inflateSync、inflatePrime、inflateMark、inflateGetHeader、inflateBackInit、inflateBack、inflateBackEnd。这些函数在源码中均以ZWRAPC_finishWithErrorMsg/ZWRAPD_finishWithErrorMsg返回Z_STREAM_ERROR并设置strm-msg如 zstd_zlibwrapper.c。因此迁移前需要审计代码确认业务路径没有调用上表不支持的函数也没有使用Z_FULL_FLUSH/Z_BLOCK/Z_TREESflush——这正是官方示例中被迫关闭test_flush用Z_FULL_FLUSH与test_sync用inflateSync的原因。在 MongoDB 仓库中进一步探索本文所有论证均可直接在仓库内验证包装层完整实现zstd_zlibwrapper.c压缩/解压分发、流类型探测、字典与 pledged size 处理、兼容性清单逐一对应公开接口与文档注释zstd_zlibwrapper.h含所有扩展 API 的语义与线程安全说明构建与测试配方zlibWrapper/Makefile双变体编译、make test、make test-valgrind官方示例examples/example.c 与原始 zlib 版本 examples/example_original.c 的 diff可精确对照一行 include 替换带来的全部差异基准工具examples/zwrapbench.c以及同一目录下的 fitblk.c、minigzip.c 等 gzip 兼容性示例。迁移落地检查清单审计 API 使用面确认项目未使用deflateCopy、inflateSync、inflateBack*等不支持函数未依赖Z_FULL_FLUSH/Z_BLOCK/Z_TREESflush替换头文件与链接全部#include zlib.h→#include zstd_zlibwrapper.h链接追加zstd_zlibwrapper.o gz*.c -lzstd保留-lz灰度启用先不加-DZWRAP_USE_ZSTD1编译运行确认行为与纯 zlib 完全一致再启用 zstd 并跑通 gz 读写、字典、大块/小块等测试路径流式小数据若单次压缩数据小于 256 KB在deflateInit/deflateReset后调用ZWRAP_setPledgedSrcSize()声明源大小批量小流用deflateReset或ZWRAP_deflateReset_keepDict复用上下文替代反复deflateInit获取约 4%~5% 的额外提速基准验收用make zwrapbench构建基准工具以-bX -eY -iZ选取级别区间、-B分块、-r递归目录量化切换前后的速度与压缩比确认收益符合预期。完成上述步骤后你的 zlib 项目即可在不改动业务逻辑的前提下平滑获得 Zstandard 的压缩性能同时保留与历史 zlib 数据的双向兼容。【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表