首页
/ OpenCV 第三方依赖 zlib-ng:IBM Z 上 DFLTCC 硬件压缩加速的完整实践

OpenCV 第三方依赖 zlib-ng:IBM Z 上 DFLTCC 硬件压缩加速的完整实践

2026-09-06 19:17:58作者:乔或婵

本文基于 OpenCV 仓库中内置的 zlib-ng 子项目 3rdparty/zlib-ng/arch/s390/README.md 编写,系统讲解 s390x(IBM Z)平台上 DFLTCC(DEFLATE CONVERSION CALL)硬件压缩/解压缩加速的启用方式、运行时行为、限制条件与 hook 宏集成机制,并结合 arch/s390 目录下的真实源码,剖析参数块结构、调优默认值、dfltcc_deflate()/dfltcc_inflate() 两大核心函数的职责划分,以及 DFLTCC 测试用自托管 CI Builder 的搭建流程。读完后你将能够在 IBM z15 及以上机型上正确开启硬件压缩,理解软硬件状态机之间的衔接原理,并掌握相关限制场景下的规避策略。

一、DFLTCC 是什么:硬件加速背景与启用方式

SystemZ(IBM Z)平台从 z15 开始提供名为 "Integrated Accelerator for zEnterprise Data Compression" 的硬件数据压缩能力,其编程接口是一条机器指令——DEFLATE CONVERSION CALL(DFLTCC),定义于 IBM《Principles of Operation》手册第 26 章。zlib-ng 在 arch/s390 目录中提供了对该指令的完整封装,使 deflate 压缩与 inflate 解压都可以交给硬件执行。

根据 README 给出的公开性能数据,启用 DFLTCC 后,压缩速度最高可获得约 110 倍的提升,解压缩速度最高可获得约 15 倍的提升(详见上游 zlib-ng 项目公布的性能基准)。

1.1 构建启用

有两种等价方式启用 DFLTCC 支持:

configure 方式:

./configure --with-dfltcc-deflate --with-dfltcc-inflate
make

CMake 方式:

cmake -DWITH_DFLTCC_DEFLATE=1 -DWITH_DFLTCC_INFLATE=1 .
make

这两组开关对应 OpenCV 仓库中 CMakeLists.txt 中的两个 option,且默认均为 OFF

option(WITH_DFLTCC_DEFLATE "Build with DFLTCC intrinsics for compression on IBM Z" OFF)
option(WITH_DFLTCC_INFLATE "Build with DFLTCC intrinsics for decompression on IBM Z" OFF)

当开关打开且目标为 s390x 时,构建系统会分别注入 S390_DFLTCC_DEFLATES390_DFLTCC_INFLATE 编译宏(见 CMakeLists.txt),由这些宏决定 hook 宏是否被替换为真实的 DFLTCC 调用。

1.2 压缩级别策略:默认只加速 level 1

按上述方式构建后,zlib-ng 的运行时策略是:

  • 压缩:仅在 level 1 使用硬件,其余所有级别回退软件实现;
  • 解压始终使用硬件。

若希望让 level 1–6 默认都走硬件压缩,可以在构建时向 CFLAGS 追加:

-DDFLTCC_LEVEL_MASK=0x7e

从源码看,DFLTCC_LEVEL_MASK 是一个位掩码,每一位对应一个压缩级别。dfltcc_detail.h 给出了全部构建期可调参数的默认值:

默认值 含义
DFLTCC_LEVEL_MASK 0x2 默认仅 level 1 使用硬件(0x7e 对应 level 1–6)
DFLTCC_BLOCK_SIZE 1048576(1 MiB) 每压缩约 1 MiB 数据开启一个新块
DFLTCC_FIRST_FHT_BLOCK_SIZE 4096 首个"固定 Huffman 表"块的长度
DFLTCC_DHT_MIN_SAMPLE_SIZE 4096 动态 Huffman 表生成的最小采样量
DFLTCC_RIBM 0 参数块中 Reserved for IBM use 字段的初始值

这些值均可在构建前通过 #define 覆盖,因此在不改动代码的情况下即可调整硬件压缩的分块与触发策略。

二、限制条件与不可复现性:必须知道的边界

2.1 压缩结果不可复现

两次输入完全相同的 DFLTCC 压缩调用,不保证产生完全相同的输出。当业务要求可复现的压缩结果(例如归档去重、内容寻址、审计校验)时需注意这一点。zlib-ng 为此提供了专有扩展接口:zng_deflateSetParams 调用中设置 Z_DEFLATE_REPRODUCIBLE 参数,即可针对特定压缩流禁用 DFLTCC,强制该流走软件压缩。

2.2 不支持的 inflate 特性

DFLTCC 并非覆盖 zlib-ng 的全部功能,以下调用不受硬件支持:

  • inflate(Z_BLOCK)inflate(Z_TREES)
  • inflateMark()
  • inflatePrime()
  • inflateSyncPoint()

遇到这些调用时,zlib-ng 会切换到软件路径;如果因流状态不允许而无法切换,则会优雅地失败(gracefully fail)而不是产生损坏数据。

三、代码结构:hook 宏如何把 s390 代码嫁接进通用 zlib-ng

所有 SystemZ 专用代码都位于 arch/s390 目录,与 zlib-ng 主代码通过一组 hook 宏 集成:主代码中到处是"若某平台支持则调用平台函数、否则走软件逻辑"的条件宏,启用 DFLTCC 编译后,这些宏被展开为对 dfltcc_* 系列函数的真实调用。

3.1 hook 宏全景

按 README 的梳理,hook 宏可分为以下几组:

窗口(window)管理。DFLTCC 要求窗口 4k 对齐且固定为 64k 大小,这与通用软件实现的窗口管理不同,由 PAD_WINDOW()WINDOW_PAD_SIZEHINT_ALIGNED_WINDOWDEFLATE_ADJUST_WINDOW_SIZE()INFLATE_ADJUST_WINDOW_SIZE() 一组宏完成对齐与尺寸调整。DEFLATE_ADJUST_WINDOW_SIZE 的具体定义见 dfltcc_deflate.h

#define DEFLATE_ADJUST_WINDOW_SIZE(n) MAX(n, HB_SIZE)

其中 HB_SIZEdfltcc_common.h 中定义为 1 << 15(32 KiB),即硬件历史缓冲区的最小尺寸。

字典翻译。软硬件窗口格式不一致,deflateSetDictionary()deflateGetDictionary()inflateSetDictionary()inflateGetDictionary() 需要特殊处理,分别由 DEFLATE_SET_DICTIONARY_HOOK()DEFLATE_GET_DICTIONARY_HOOK()INFLATE_SET_DICTIONARY_HOOK()INFLATE_GET_DICTIONARY_HOOK() 触发。前两个宏的展开逻辑可以在 dfltcc_deflate.h 中直接看到:先询问 dfltcc_can_deflate() 该流是否可走硬件,是则调用 dfltcc_deflate_set_dictionary() / dfltcc_deflate_get_dictionary() 做软硬件字典格式互转,否则落回软件实现。

状态重置deflateResetKeep()inflateResetKeep() 通过 DEFLATE_RESET_KEEP_HOOK() / INFLATE_RESET_KEEP_HOOK() 同步更新 DFLTCC 参数块(对应 dfltcc_deflate.h 中的 dfltcc_reset_deflate_state)。

优雅失败INFLATE_PRIME_HOOK()INFLATE_MARK_HOOK()INFLATE_SYNC_POINT_HOOK() 让上述三个不受支持的调用在 DFLTCC 流上以错误码形式干净地失败。

压缩参数切换DEFLATE_PARAMS_HOOK() 实现流中途(mid-stream)在硬件/软件压缩之间切换:切换通常需要冲刷当前块,在低内存情况下可能无法完成,因此 deflateParams() 通过 DEFLATE_DONE() hook 检测并妥善处理这类场景(见 dfltcc_deflate.h)。

输出边界计算。硬件与软件的压缩比不同,DEFLATE_BOUND_ADJUST_COMPLEN()DEFLATE_NEED_CONSERVATIVE_BOUND() 使 deflateBound() 对硬件路径返回正确上界。硬件路径的压缩输出上界由 dfltcc_common.hDEFLATE_BOUND_COMPLEN() 按位精确计算:块头 3 位 + HLIT/HDIST/HCODE 计数位 + 码长编码(最多 19 个码长 × 3 位)+ (286+30) 个符号 × 最多 16 位 + EOBS(最多 15 位)+ 填充(最多 7 位)。

压缩/解压主入口与校验和抑制。实际的压缩与解压缩分别由 DEFLATE_HOOK()INFLATE_TYPEDO_HOOK() 完成。由于 DFLTCC 解压时自行管理滑动窗口,updatewindow() 调用被 INFLATE_NEED_UPDATEWINDOW() 宏抑制。又因为 DFLTCC 在压缩的同时顺带计算 CRC-32 与 Adler-32,只要硬件路径生效,软件侧校验和计算就用 DEFLATE_NEED_CHECKSUM() / INFLATE_NEED_CHECKSUM() 宏关闭,避免重复计算(对应 dfltcc_deflate.h 中的 !dfltcc_can_deflate(strm))。

可复现性开关DEFLATE_CAN_SET_REPRODUCIBLE() 宏判定在流的某个时刻能否切换 Z_DEFLATE_REPRODUCIBLE 设置——压缩开始前总能设置,但 deflate 流中途并不总是允许。

3.2 参数块与状态扩展

DFLTCC 指令接收一个参数块(parameter block)、输入缓冲区、输出缓冲区和窗口。参数块与 zlib 流状态体(stream state)并排存放。struct dfltcc_param_v0 的完整定义见 dfltcc_common.h,关键字段包括:

  • cf(Continuation Flag):跨调用的续压标志;
  • cvt(Check Value Type):CVT_CRC32/CVT_ADLER32,选择校验和算法(见 dfltcc_detail.h);
  • htt(Huffman-Table Type):固定表/动态表;
  • bcc/bhf/bcf:块收尾控制、块头最终位、块续压标志;
  • sbb(Sub-Byte Boundary):对应软件侧的 bi_valid
  • hl/ho:历史长度/历史偏移,对应软件侧的 whave/wnext
  • cv:校验值(CRC-32 或 Adler-32 累计结果);
  • cdht[288]:压缩的动态 Huffman 表;
  • csb[1152]:Continuation-State Buffer,续压状态缓冲。

此外,硬件流状态在通用 deflate_state/inflate_state 基础上扩展为 arch_deflate_state / arch_inflate_state(见 dfltcc_common.h),其中 arch_deflate_state 携带 level_maskblock_sizeblock_thresholddht_threshold 四个字段,恰好对应第一节表格中的调优参数。

功能启用探测方面,dfltcc_detail.h 通过 STFLE 指令读取 CPU 设施位图,检查 facility 151 是否置位,以此判断硬件是否真正支持 DFLTCC——这意味着即使库是按 DFLTCC 构建的,在不支持该指令的 z/Architecture 机器上运行时也会自动退回软件路径。

四、核心状态机:dfltcc_deflate()dfltcc_inflate()

当以 DFLTCC 方式构建时,上述 hook 宏被转换为对 arch/s390/dfltcc_* 文件中函数的调用。这些函数分三类:

  1. DFLTCC 基础封装——如 dfltcc(),直接包装机器指令;
  2. 软硬件数据格式翻译——如 dfltcc_deflate_set_dictionary()
  3. 软硬件状态机翻译——如 dfltcc_deflate()dfltcc_inflate()

前两类相对简单;由于软硬件两套状态机都存在各种"怪癖"(quirk),第三类相当复杂。

4.1 dfltcc_deflate() 的九项职责

dfltcc_deflate()deflate() 调用,负责:

  • 可用性判定:判断当前流能否使用 DFLTCC,不能则返回 0,deflate() 转而去软件压缩函数;否则返回 1;
  • 块管理与 Huffman 表决策:DFLTCC 只有被软件显式指示才会结束块,且必须决定用固定表还是动态表。由于"查看数据以收集统计"会抵消硬件加速收益,采取的策略是:DFLTCC_FIRST_FHT_BLOCK_SIZE(默认 4096)字节放入固定表块,其后每 DFLTCC_BLOCK_SIZE(默认 1 MiB)字节放入动态表块
  • EOBS(块结束符)写入:参数块的 Block Closing Control 位可指示 DFLTCC 写 EOBS,但要求输入长度非零或 Continuation Flag 已置位——换言之,如果 EOBS 是 DFLTCC 唯一要做的事,它会静默拒绝。因此代码干脆从不使用该位,而是用 soft_bcc 变量自行控制何时写 EOBS;
  • 块后处理触发:根据 flush 模式,块或流结束时 deflate() 要做各种附加动作,dfltcc_deflate() 通过 block_state *result 参数把这些"待办"告知 deflate()
  • 状态字段互转:软件状态字段 ↔ 硬件参数块字段,例如 wrap ↔ Check Value Type、bi_valid ↔ Sub-Byte Boundary;某些字段(如 Continuation Flag、Continuation State Buffer)不可翻译,必须在多次调用间原样保留在参数块中;
  • flush 模式与低内存处理:两者深度交织。总原则是:当 Continuation Flag 置位时,软件侧不能做任何事——无论显式调用 send_eobs(),还是隐式地带着某些返回码和 *result 值回到 deflate()
  • 流结束处理:新块开始且 flush 模式为 Z_FINISH 时,用 Block Header Final 位把该块标记为最终块;但有时需要一个空最终块,而 DFLTCC 和 EOBS 一样会静默拒绝只写空块。为此代码"假装自己不支持 DFLTCC",诱使 deflate() 调用软件压缩函数去写空最终块,该路径由 need_empty_block 变量控制;
  • 错误处理:把 Operation-Ending-Supplemental Code(OESC)转成字符串。此类错误只会由内存损坏之类的异常引起,因此不影响 deflate() 的返回码。

dfltcc_deflate.c 中可以看到这些调优参数在流初始化时被装入状态:level_mask = DFLTCC_LEVEL_MASKblock_size = DFLTCC_BLOCK_SIZEblock_threshold = DFLTCC_FIRST_FHT_BLOCK_SIZEneed_empty_blocksoft_bcc 等局部变量则出现在压缩主循环的块收尾分支中(dfltcc_deflate.c),与上文描述一一对应。

4.2 dfltcc_inflate() 的五项职责

dfltcc_inflate()inflate()TYPEDO 状态(元数据已全部解析、流定位在 deflate 块头的类型位处)调用,负责:

  • 降级到软件:当 flush 模式为 Z_BLOCKZ_TREES 时必须回退——DFLTCC 没有"在块边界或树边界停止解压"的能力;
  • 解压循环管理:通过返回值 DFLTCC_INFLATE_BREAKDFLTCC_INFLATE_CONTINUE 控制 inflate() 的解压循环是否继续;
  • 状态字段互转:如 whave ↔ History Length、wnext ↔ History Offset;
  • 流结束:通过 last 状态字段指示 inflate() 返回 Z_STREAM_END
  • 错误处理:与压缩侧一样做 OESC 到字符串的转换,但区别在于解压错误可能由坏输入引起,因此会把 mode 字段置为 MEMBAD 传播回 inflate(),最终体现为标准的错误返回码。

五、测试 DFLTCC:需要真机 z15 与自托管 CI Builder

DFLTCC 指令的复杂性决定了 QEMU TCG 短期内无法模拟它(截至文档撰写时)。要测试 DFLTCC 支持,必须拥有 IBM z15 或更新机型的 VM 或 LPAR。由于 DFLTCC 是非特权指令,无需特殊 VM/LPAR 配置,也无需 root 权限

zlib-ng 的 CI 使用一台 IBM 提供的 z15 自托管构建机(self-hosted builder)做 DFLTCC 测试。由于 IBM Z 没有官方 GitHub Actions runner,该构建机参考 anup-kodlekere/gaplib 项目搭建,actions-runner 后续升级可能需要同步更新补丁;其中 .NET 版本号补丁被单独拆出,避免补丁频繁变动。

5.1 配置构建机

安装前置依赖:

sudo dnf install podman

添加 actions-runner 服务:

sudo cp self-hosted-builder/actions-runner.service /etc/systemd/system/
sudo systemctl daemon-reload

创建配置文件(需要一个具有 repo 权限范围的 GitHub personal access token):

# 创建 /etc/actions-runner
repo=<owner>/<name>
access_token=<ghp_***>

自动启动:

sudo systemctl enable --now actions-runner

5.2 重建容器

为获取最新 OS 安全更新等,可按下述步骤重建 gaplib-actions-runner podman 容器:

# 停止 actions-runner 服务
sudo systemctl stop actions-runner

# 删除旧容器
sudo podman container rm gaplib-actions-runner

# 删除旧镜像
sudo podman image rm localhost/zlib-ng/actions-runner

# 构建镜像
sudo podman build --squash -f Dockerfile.zlib-ng --tag zlib-ng/actions-runner --build-arg .

# 创建容器
sudo podman create --name=gaplib-actions-runner --env-file=/etc/actions-runner \
    --init --interactive --volume=actions-runner-temp:/home/actions-runner zlib-ng/actions-runner

# 启动 actions-runner 服务
sudo systemctl start actions-runner

在 OpenCV 仓库中,构建机的全部资产实际位于 self-hosted-builder 目录,包含 actions-runner.Dockerfile(runner 镜像定义)、actions-runner.service(systemd 服务单元)、runner-global.json(runner 全局配置模板)与 runner-s390x.patch(s390x 平台适配补丁),与 README 中上述步骤一一对应。

六、目录速查:arch/s390 各文件的职责

最后给出一张文件级速查表,方便在 OpenCV 仓库中定位与深入:

文件 职责
dfltcc_common.h QAF/参数块结构定义、dfltcc_statearch_*_state 扩展、历史缓冲区与块大小常量、DEFLATE_BOUND_COMPLEN
dfltcc_detail.h 构建期调优默认值、STFLE 设施探测、OESC 错误码转字符串
dfltcc_deflate.c / dfltcc_deflate.h 硬件压缩主状态机及全部 deflate 侧 hook 宏展开
dfltcc_inflate.c / dfltcc_inflate.h 硬件解压主状态机及 inflate 侧 hook
s390_features.c / s390_features.h s390 特性探测(当前仅探测向量扩展 has_vx
crc32-vx.c 基于 s390 向量扩展(Vector Facility)的 CRC-32 实现,独立于 DFLTCC 的另一条 s390 加速路径
Makefile.in configure 构建入口,编译 s390_featuresdfltcc_deflatedfltcc_inflatecrc32-vx 四个目标,其中 crc32-vx 额外启用 VGFMAFLAG(向量 GFMA)编译标志

需要强调:DFLTCC 加速是 zlib-ng 面向 IBM Z 的平台特定优化,OpenCV 在 s390x 平台链接该第三方 zlib 时即可获得;在 x86_64、ARM 等不具备 DFLTCC 指令的平台上,相关 hook 宏不会被展开为硬件调用,zlib-ng 自动运行在纯软件路径上,功能与接口完全不受影响。若你的业务运行在 IBM Z 上且对压缩吞吐敏感,按第一节的开关开启 DFLTCC,并注意第二节所述的不可复现性与 API 限制,即可安全地获得数量级的压缩/解压加速。

登录后查看全文
热门项目推荐
相关项目推荐