# TAKTショーケース:bzip2の展開(Silesiaコーパス) 1つのプログラムを2通りにビルドしたものです。`bz2dec`は`bzip2 -dc`と同様に、`.bz2`ファイルを展開して標準出力に書き出します。`bz2dec-original`はcrates.ioで公開されている[bzip2-rs](https://crates.io/crates/bzip2-rs) 0.1.2クレート(純Rust実装のbzip2デコーダ)をそのまま使い、`bz2dec-takt`はTAKTで最適化した同じクレートを使います。ドライバは同一のソースファイル、ビルド手順も同一で、両プログラムの出力はバイト単位で一致します。ビルド済みの両プログラム、オリジナルビルドのソースコード、コーパスを取得するスクリプト、測定と検証を再現するスクリプトを収録しています。 ## ベンチマークの内容 [Silesiaコーパス](https://sun.aei.polsl.pl/~sdeor/index.php?page=silesia)の12ファイル(テキスト、実行ファイル、データベース、画像、XML。計211,938,580バイト)を、それぞれPythonの`bz2.compress(data, 9)`(libbz2 1.0.8、1ファイル1ストリーム、合計54,506,769バイト)で圧縮し、1つのプロセスで展開します: ```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、専用コア1基(SMTの兄弟スレッドはアイドル、そのCCXは測定専用に確保)。`perf stat`で展開プロセスのユーザーモードのサイクル数と命令数、および実時間を計測しました。交互実行9ラウンド(ラウンドごとにプログラムの実行順を入れ替え)の中央値です。測定したのは、ここにあるファイルそのものです。3つのプログラムはいずれもglibcに動的リンクされた実行ファイルで、`bz2dec`の2つのビルドは同じ手順でビルドしています:Rust 1.96.0、LTO、codegen-units 1、`target-cpu=x86-64-v3`、panic abort、シンボル除去(strip)。 全ファイルを1プログラムにつき1プロセスで処理した場合: | プログラム | デコーダ | サイクル数(百万) | 命令数(百万) | 時間(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で最適化した同じクレート | 5,104.7 | 10,028.5 | 1,204 | 176.0 | 3.50× | C版`bzip2 -dc`に対する`bz2dec-takt`の高速化は、サイクル数で4.14倍、時間で4.06倍です。`bz2dec-original`に対しては、時間で3.43倍です。高速化はサイクル数の中央値の比、MB/sは実時間1秒あたりの展開後のメガバイト数(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`入力が、当社が測定したものとバイト単位で同一かどうかも表示します。`tools/run_bench.sh`は、`perf`が使える環境ではサイクル数も計測します。 使い方:`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の12ファイル:`bz2dec-original`、`bz2dec-takt`、`bzip2 -dc`の出力は、ファイルごとに処理した場合も、12ファイルを1回で処理した場合も、元のファイルとバイト単位で一致します。 - 追加の12,900ストリーム:正常なもの1,200件(コーパスのファイルを無作為に切り出したもの、圧縮レベル1〜9)と、破損したもの11,700件(完全な入力を破損させたもの900件、残りは切り出したデータを破損させたもの。ビット反転、切り詰め、バイト置換、範囲のゼロ埋め、範囲の挿入と削除)。すべてのストリームについて、2つのプログラムの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`をTAKTでビルドしたクレートとリンクしたもので、クレートの公開API、エラー型、メッセージはそのまま保たれています。 ## 収録内容 - `bin/`:`bz2dec-original`と`bz2dec-takt` - `source/`:`bz2dec-original`のビルド元となるドライバとCargoファイル - `tools/`:`fetch_silesia.py`(コーパスと入力)、`run_bench.sh`(検証と時間計測)、`compare_corrupted.py`(正常なストリームと破損したストリーム) - `measurements.json`、`SHA256SUMS`、`LICENSE` TAKTでビルドしたbzip2-rsのソースコードは公開していません。当社が納品するのはビルドです。