Витрина TAKT: распаковка bzip2 (корпус Silesia)
Одна программа в двух сборках: bz2dec распаковывает файлы .bz2 в стандартный вывод, как bzip2 -dc.
bz2dec-original использует крейт bzip2-rs 0.1.2 (декодер bzip2
на чистом Rust) в том виде, в каком он опубликован на crates.io; bz2dec-takt — тот же крейт после
оптимизации TAKT. Драйвер — один и тот же исходный файл, рецепт сборки тот же, вывод обеих программ
совпадает до байта. Здесь — обе готовые программы, исходник оригинальной сборки, скрипт, который
скачивает корпус, и скрипты, повторяющие замер и проверки.
Что делает бенчмарк
Двенадцать файлов корпуса 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, символы удалены.
Весь набор, один процесс на программу:
| Программа | Декодер | Такты, млн | Инструкции, млн | Время, мс | МБ/с | Ускорение |
|---|---|---|---|---|---|---|
bz2dec-original |
bzip2-rs 0.1.2 с 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) | 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× |
bz2dec-takt против bzip2 -dc на C: 4,14× по тактам, 4,06× по времени.
Против bz2dec-original по времени: 3,43×. Ускорение — отношение медиан тактов; МБ/с — распакованные
мегабайты (10⁶ байт) в секунду реального времени.
Каждый файл в отдельном процессе (те же раунды; время в мс, ускорение по тактам):
| Файл | Без сжатия, МБ | Оригинал, мс | TAKT, мс | bzip2 -dc, мс |
Против оригинала | Против bzip2 -dc |
|---|---|---|---|---|---|---|
| dickens | 10,2 | 247,3 | 65,8 | 286,7 | 4,16× | 4,82× |
| mozilla | 51,2 | 1 048,6 | 370,9 | 1 272,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× против оригинала и от 3,43× до 4,87×
против bzip2 -dc. Реальное время включает также запуск процесса, чтение входа и работу ядра ОС,
одинаковые для всех программ, поэтому отношения по времени немного ниже отношений по тактам. На
других данных и других процессорах числа будут другими. Подробности и все отсчёты — в
measurements.json.
Запуск
python3 tools/fetch_silesia.py # скачивает silesia.zip (68 МБ), проверяет его, пишет data/raw и data/bz2
tools/run_bench.sh 7 2 # проверяет каждый байт вывода, затем 7 чередующихся раундов на ядре 2
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 — ошибка
использования, ввода-вывода или CPU; 2 — некорректный поток (сообщение декодера печатается в stderr);
3 — внутренняя ошибка декодера.
Linux x86-64 (glibc). Обе программы собраны для x86-64-v3: AVX2, BMI1, BMI2, FMA (Intel Haswell и новее, AMD Zen и новее); на более старых процессорах они не запускаются.
Эквивалентность
- Двенадцать файлов 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(использовались seed 1 и 2, см.measurements.json).
Сборка оригинала
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, совпадающий бит в бит (Rust 1.96.0 из rustup,
в другом каталоге). bin/bz2dec-takt — тот же source/src/main.rs, скомпонованный со сборкой TAKT
этого крейта; она сохраняет публичный API крейта, тип ошибки и сообщения.
Состав
bin/:bz2dec-originalиbz2dec-takt;source/: драйвер и файлы Cargo, из которых собираетсяbz2dec-original;tools/:fetch_silesia.py(корпус и входы),run_bench.sh(проверка и замер времени),compare_corrupted.py(корректные и повреждённые потоки);measurements.json,SHA256SUMS,LICENSE.
Исходный код сборки TAKT для bzip2-rs не публикуется: мы поставляем сборки.