← back to the showcase

TAKT showcase: bzip2 decompression (Silesia corpus)

One program in two builds: bz2dec decompresses .bz2 files to standard output, like bzip2 -dc. bz2dec-original uses the crate bzip2-rs 0.1.2 (a pure Rust bzip2 decoder) as published on crates.io; bz2dec-takt uses the same crate after TAKT optimization. The driver is the same source file, the build recipe is the same, and the output of both programs is identical to the byte. Here you get both ready programs, the source of the original build, a script that fetches the corpus and the scripts that repeat the measurement and the checks.

What the benchmark does

The twelve files of the Silesia corpus (text, executables, databases, images, XML; 211,938,580 bytes), each compressed with Python's bz2.compress(data, 9) (libbz2 1.0.8, one stream per file, 54,506,769 bytes in total), are decompressed in one process:

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

The system bzip2 -dc (the reference C implementation) runs the same job for comparison.

Measurement (TAKT rig)

AMD Threadripper PRO 5975WX (Zen 3), Linux, one dedicated core (its SMT sibling idle, its CCX reserved for the measurement). perf stat counts user-mode cycles and instructions of the decoding process and its wall time; 9 interleaved rounds (the program order rotates from round to round), median. Exactly these files were measured. All three programs are dynamically linked glibc executables; both bz2dec builds use the same recipe: Rust 1.96.0, LTO, codegen-units 1, target-cpu=x86-64-v3, panic abort, stripped.

The whole set, one process per program:

Program Decoder Cycles, M Instructions, M Time, ms MB/s Speedup
bz2dec-original bzip2-rs 0.1.2 from crates.io 17,867.0 22,191.1 4,129 51.3 1.00×
bzip2 -dc C bzip2 / libbz2 1.0.8 (Ubuntu 24.04 package) 21,148.9 25,378.5 4,884 43.4 0.84×
bz2dec-takt the same crate after TAKT optimization 5,104.7 10,028.5 1,204 176.0 3.50×

bz2dec-takt against the C bzip2 -dc: 4.14× by cycles, 4.06× by time. Against bz2dec-original by time: 3.43×. Speedup is the ratio of median cycles; MB/s is decompressed megabytes (10⁶ bytes) per second of wall time.

Every file in its own process (same rounds; time in ms, speedup by cycles):

File Raw, MB Original, ms TAKT, ms bzip2 -dc, ms vs original vs 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×

The speedup depends on the data: from 2.96× to 4.32× against the original and from 3.43× to 4.87× against bzip2 -dc on these files. Wall time also includes process start, reading the input and kernel work, which are the same for every program; that is why the ratios by time are a little lower than the ratios by cycles. Other data and other CPUs will give other numbers. Details and every sample are in measurements.json.

Running

python3 tools/fetch_silesia.py      # downloads silesia.zip (68 MB), checks it, writes data/raw and data/bz2
tools/run_bench.sh 7 2              # checks every output byte, then 7 interleaved rounds on core 2
bin/bz2dec-takt -c data/bz2/dickens.bz2 | cmp - data/raw/dickens      # identical to the byte
sha256sum -c SHA256SUMS

tools/fetch_silesia.py needs only Python 3 and checks the archive (sha256) and every file (size, the MD5 published on the corpus page, sha256); it also tells you whether your .bz2 inputs are the exact bytes we measured. tools/run_bench.sh counts cycles too when perf is available.

Usage: bz2dec -c FILE.bz2 [FILE.bz2 ...] > OUT. Exit codes: 0 success; 1 usage, I/O or CPU error; 2 invalid stream (the decoder's error message is printed to stderr); 3 internal decoder error.

Linux x86-64 (glibc). Both programs are built for x86-64-v3: AVX2, BMI1, BMI2, FMA (Intel Haswell and newer, AMD Zen and newer); they do not run on older CPUs.

Equivalence

Building the original

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

In our check this rebuilt bin/bz2dec-original bit for bit (Rust 1.96.0 from rustup, in another directory). bin/bz2dec-takt is the same source/src/main.rs linked with the TAKT build of the crate, which keeps the crate's public API, error type and messages.

Contents

The source of the TAKT build of bzip2-rs is not published: we deliver builds.

Source text: README.md

Telegram