← 返回案例展示

TAKT 案例展示:bzip2 解压(Silesia 语料库)

同一个程序的两个构建:bz2dec 将 .bz2 文件解压到标准输出,类似 bzip2 -dc。bz2dec-original 使用 crates.io 上发布的原版 crate bzip2-rs 0.1.2(纯 Rust 实现的 bzip2 解码器);bz2dec-takt 使用经 TAKT 优化后的同一 crate。两者的驱动程序是同一个源文件,构建方案相同,输出逐字节一致。本包提供这两个可直接运行的程序、原始构建的源代码、下载语料库的脚本,以及用于复现测量和检查的脚本。

基准测试的内容

Silesia 语料库的十二个文件(文本、可执行文件、数据库、图像、XML;共 211,938,580 字节)分别用 Python 的 bz2.compress(data, 9) 压缩(libbz2 1.0.8,每个文件一个数据流,合计 54,506,769 字节),然后在同一个进程中解压:

bz2dec -c dickens.bz2 mozilla.bz2 mr.bz2 nci.bz2 ooffice.bz2 osdb.bz2 reymont.bz2 \
          samba.bz2 sao.bz2 webster.bz2 xml.bz2 x-ray.bz2 > /dev/null

作为对比,系统自带的 bzip2 -dc(C 语言参考实现)执行同样的任务。

测量(TAKT 测试台)

AMD Threadripper PRO 5975WX(Zen 3),Linux,一个专用核心(其 SMT 兄弟线程保持空闲,所在 CCX 专供测量使用)。perf stat 统计解码进程的用户态时钟周期和指令数,并记录其墙钟时间;9 轮交替测量(每轮轮换程序的运行顺序),取中位数。所测文件正是本包中的这些文件。三个程序均为动态链接 glibc 的可执行文件;两个 bz2dec 构建采用相同的构建方案:Rust 1.96.0、LTO、codegen-units 1、target-cpu=x86-64-v3、panic abort、去除符号。

整个数据集,每个程序一个进程:

程序 解码器 时钟周期(百万) 指令数(百万) 时间(ms) MB/s 加速
bz2dec-original crates.io 上的 bzip2-rs 0.1.2 17,867.0 22,191.1 4,129 51.3 1.00×
bzip2 -dc C bzip2 / libbz2 1.0.8(Ubuntu 24.04 软件包) 21,148.9 25,378.5 4,884 43.4 0.84×
bz2dec-takt 经 TAKT 优化后的同一 crate 5,104.7 10,028.5 1,204 176.0 3.50×

bz2dec-takt 相对 C 语言的 bzip2 -dc:按时钟周期为 4.14×,按时间为 4.06×。相对 bz2dec-original,按时间为 3.43×。加速为时钟周期中位数之比;MB/s 为每秒墙钟时间解压出的兆字节数(10⁶ 字节)。

每个文件单独一个进程(轮次相同;时间单位为 ms,加速按时钟周期计):

文件 解压后大小(MB) 原始版本(ms) TAKT(ms) bzip2 -dc(ms) 相对原始版本 相对 bzip2 -dc
dickens 10.2 247.3 65.8 286.7 4.16× 4.82×
mozilla 51.2 1048.6 370.9 1272.9 2.96× 3.64×
mr 10.0 177.2 48.4 206.1 4.04× 4.64×
nci 33.6 461.7 111.0 516.6 4.32× 4.81×
ooffice 6.2 169.4 59.0 205.5 3.20× 3.95×
osdb 10.1 229.3 63.0 265.5 3.92× 4.68×
reymont 6.6 136.0 35.8 152.9 4.25× 4.87×
samba 21.6 333.1 115.0 404.9 3.08× 3.75×
sao 7.3 241.3 85.7 270.0 3.05× 3.43×
webster 41.5 843.6 216.1 972.2 4.10× 4.79×
xml 5.3 72.3 20.9 82.6 4.04× 4.64×
x-ray 8.5 236.4 80.1 255.6 3.22× 3.50×

加速取决于数据:在这些文件上,相对原始版本为 2.96× 至 4.32×,相对 bzip2 -dc 为 3.43× 至 4.87×。墙钟时间还包括进程启动、读取输入和内核工作,这些开销对每个程序都相同;因此按时间计算的比值略低于按时钟周期计算的比值。其他数据和其他 CPU 会得到不同的数字。详细信息和全部样本见 measurements.json。

运行

python3 tools/fetch_silesia.py      # 下载 silesia.zip(68 MB),校验后写入 data/raw 和 data/bz2
tools/run_bench.sh 7 2              # 先检查每个输出字节,然后在核心 2 上进行 7 轮交替测量
bin/bz2dec-takt -c data/bz2/dickens.bz2 | cmp - data/raw/dickens      # 逐字节一致
sha256sum -c SHA256SUMS

tools/fetch_silesia.py 只需要 Python 3,它会校验压缩包(sha256)和每个文件(大小、语料库页面公布的 MD5、sha256),还会告诉您,您的 .bz2 输入是否与我们测量所用的字节完全相同。如果系统中有 perf,tools/run_bench.sh 还会统计时钟周期。

用法:bz2dec -c FILE.bz2 [FILE.bz2 ...] > OUT。退出码:0 表示成功;1 表示用法、I/O 或 CPU 错误;2 表示数据流无效(解码器的错误信息输出到 stderr);3 表示解码器内部错误。

Linux x86-64(glibc)。两个程序均针对 x86-64-v3 构建:AVX2、BMI1、BMI2、FMA(Intel Haswell 及更新、AMD Zen 及更新的处理器);无法在更早的 CPU 上运行。

等价性

构建原始版本

cd source
RUSTFLAGS="-C target-cpu=x86-64-v3 --remap-path-prefix=$(ls -d ~/.cargo/registry/src/index.crates.io-*)=. \
  --remap-path-prefix=$HOME/.rustup=. --remap-path-prefix=$PWD=." cargo +1.96.0 build --release --locked
strip --strip-all target/release/bz2dec

在我们的检查中,这样重新构建出的 bin/bz2dec-original 逐位一致(使用 rustup 安装的 Rust 1.96.0,在另一个目录中构建)。bin/bz2dec-takt 由同一份 source/src/main.rs 与该 crate 的 TAKT 构建链接而成,后者保留了 crate 的公共 API、错误类型和错误信息。

包内容

bzip2-rs 的 TAKT 构建的源代码不公开:我们交付的是构建产物。

原文: README.zh.md

Telegram