# TAKT 案例展示:bzip2 解压(Silesia 语料库) 同一个程序的两个构建:`bz2dec` 将 `.bz2` 文件解压到标准输出,类似 `bzip2 -dc`。`bz2dec-original` 使用 crates.io 上发布的原版 crate [bzip2-rs](https://crates.io/crates/bzip2-rs) 0.1.2(纯 Rust 实现的 bzip2 解码器);`bz2dec-takt` 使用经 TAKT 优化后的同一 crate。两者的驱动程序是同一个源文件,构建方案相同,输出逐字节一致。本包提供这两个可直接运行的程序、原始构建的源代码、下载语料库的脚本,以及用于复现测量和检查的脚本。 ## 基准测试的内容 [Silesia 语料库](https://sun.aei.polsl.pl/~sdeor/index.php?page=silesia)的十二个文件(文本、可执行文件、数据库、图像、XML;共 211,938,580 字节)分别用 Python 的 `bz2.compress(data, 9)` 压缩(libbz2 1.0.8,每个文件一个数据流,合计 54,506,769 字节),然后在同一个进程中解压: ```sh 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`。 ## 运行 ```sh 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 上运行。 ## 等价性 - 十二个 Silesia 文件:`bz2dec-original`、`bz2dec-takt` 和 `bzip2 -dc` 的输出与原始文件逐字节一致,无论是逐个文件运行,还是十二个文件一次运行。 - 另外 12,900 个数据流:1,200 个有效数据流(语料库文件的随机片段,压缩级别 1–9)和 11,700 个损坏的数据流(900 个是完整输入的损坏副本,其余为损坏的片段:位翻转、截断、字节替换、区间清零、插入和删除区间)。对每个数据流,两个程序的 stdout、stderr 和退出码均完全一致(退出码 0:1,360 个数据流,退出码 2:11,540 个);不一致数为 0。可用 `python3 tools/compare_corrupted.py bin/bz2dec-original bin/bz2dec-takt data --seed 1` 复现(我们使用了种子 1 和 2,见 `measurements.json`)。 ## 构建原始版本 ```sh 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、错误类型和错误信息。 ## 包内容 - `bin/`:`bz2dec-original` 和 `bz2dec-takt`; - `source/`:驱动程序和 Cargo 文件,`bz2dec-original` 由此构建; - `tools/`:`fetch_silesia.py`(语料库和输入)、`run_bench.sh`(检查和计时)、`compare_corrupted.py`(有效和损坏的数据流); - `measurements.json`、`SHA256SUMS`、`LICENSE`。 bzip2-rs 的 TAKT 构建的源代码不公开:我们交付的是构建产物。