Vetrina TAKT: decompressione bzip2 (corpus Silesia)
Un solo programma in due build: bz2dec decomprime file .bz2 sullo standard output, come bzip2 -dc.
bz2dec-original usa il crate bzip2-rs 0.1.2 (un decoder bzip2
in puro Rust) così come è pubblicato su crates.io; bz2dec-takt usa lo stesso crate dopo
l’ottimizzazione TAKT. Il driver è lo stesso file sorgente, la ricetta di build è la stessa e
l’output dei due programmi è identico al byte. Qui si trovano i due programmi pronti, il sorgente
della build originale, uno script che scarica il corpus e gli script che ripetono la misura e le
verifiche.
Che cosa fa il benchmark
I dodici file del corpus Silesia
(testo, eseguibili, database, immagini, XML; 211.938.580 byte), ciascuno compresso con
bz2.compress(data, 9) di Python (libbz2 1.0.8, uno stream per file, 54.506.769 byte in totale),
vengono decompressi in un unico processo:
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
Per confronto, lo stesso lavoro viene eseguito dal bzip2 -dc di sistema (l’implementazione C di
riferimento).
Misura (banco TAKT)
AMD Threadripper PRO 5975WX (Zen 3), Linux, un core dedicato (il suo gemello SMT inattivo, il suo
CCX riservato alla misura). perf stat conta i cicli e le istruzioni in modalità utente del
processo di decodifica e il suo tempo reale; 9 round alternati (l’ordine dei programmi ruota da un
round all’altro), mediana. Sono stati misurati esattamente questi file. Tutti e tre i programmi
sono eseguibili glibc a collegamento dinamico; entrambe le build di bz2dec usano la stessa
ricetta: Rust 1.96.0, LTO, codegen-units 1, target-cpu=x86-64-v3, panic abort, simboli rimossi.
L’intero set, un processo per programma:
| Programma | Decoder | Cicli, mln | Istruzioni, mln | Tempo, ms | MB/s | Accelerazione |
|---|---|---|---|---|---|---|
bz2dec-original |
bzip2-rs 0.1.2 da crates.io | 17.867,0 | 22.191,1 | 4.129 | 51,3 | 1,00× |
bzip2 -dc |
bzip2 / libbz2 1.0.8 in C (pacchetto Ubuntu 24.04) | 21.148,9 | 25.378,5 | 4.884 | 43,4 | 0,84× |
bz2dec-takt |
lo stesso crate dopo l’ottimizzazione TAKT | 5.104,7 | 10.028,5 | 1.204 | 176,0 | 3,50× |
bz2dec-takt rispetto al bzip2 -dc in C: 4,14× in cicli, 4,06× in tempo.
Rispetto a bz2dec-original in tempo: 3,43×. L’accelerazione è il rapporto tra le mediane dei
cicli; MB/s indica i megabyte decompressi (10⁶ byte) per secondo di tempo reale.
Ogni file in un processo separato (stessi round; tempo in ms, accelerazione in cicli):
| File | Non compresso, MB | Originale, ms | TAKT, ms | bzip2 -dc, ms |
rispetto all’originale | rispetto a 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× |
L’accelerazione dipende dai dati: su questi file va da 2,96× a 4,32× rispetto all’originale e da
3,43× a 4,87× rispetto a bzip2 -dc. Il tempo reale comprende anche l’avvio del processo, la
lettura dell’input e il lavoro del kernel, che sono uguali per ogni programma; per questo i
rapporti in tempo sono un po’ più bassi dei rapporti in cicli. Altri dati e altre CPU daranno altri
numeri. I dettagli e tutti i campioni sono in measurements.json.
Esecuzione
python3 tools/fetch_silesia.py # scarica silesia.zip (68 MB), lo verifica, scrive data/raw e data/bz2
tools/run_bench.sh 7 2 # verifica ogni byte in uscita, poi 7 round alternati sul core 2
bin/bz2dec-takt -c data/bz2/dickens.bz2 | cmp - data/raw/dickens # identico al byte
sha256sum -c SHA256SUMS
tools/fetch_silesia.py richiede solo Python 3 e verifica l’archivio (sha256) e ogni file
(dimensione, l’MD5 pubblicato sulla pagina del corpus, sha256); indica anche se i suoi input .bz2
sono esattamente i byte che abbiamo misurato. tools/run_bench.sh conta anche i cicli quando
perf è disponibile.
Uso: bz2dec -c FILE.bz2 [FILE.bz2 ...] > OUT. Codici di uscita: 0 successo; 1 errore di utilizzo,
di I/O o della CPU; 2 stream non valido (il messaggio di errore del decoder viene stampato su
stderr); 3 errore interno del decoder.
Linux x86-64 (glibc). Entrambi i programmi sono compilati per x86-64-v3: AVX2, BMI1, BMI2, FMA (Intel Haswell e successivi, AMD Zen e successivi); non funzionano su CPU più vecchie.
Equivalenza
- I dodici file Silesia: l’output di
bz2dec-original,bz2dec-taktebzip2 -dccoincide con il file non compresso byte per byte, file per file e in un’unica esecuzione sui dodici file. - Altri 12.900 stream: 1.200 validi (porzioni casuali dei file del corpus, livelli di compressione
1–9) e 11.700 corrotti (900 copie corrotte degli input completi, il resto porzioni corrotte:
inversione di bit, troncamento, sostituzione di byte, intervalli azzerati, intervalli inseriti ed
eliminati). Per ogni stream stdout, stderr e codice di uscita dei due programmi sono identici
(codice di uscita 0: 1.360 stream, codice di uscita 2: 11.540); 0 discrepanze. Per ripetere la
verifica:
python3 tools/compare_corrupted.py bin/bz2dec-original bin/bz2dec-takt data --seed 1(sono stati usati i seed 1 e 2, si vedameasurements.json).
Build dell’originale
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
Nella nostra verifica questa procedura ha ricostruito bin/bz2dec-original bit per bit (Rust 1.96.0
da rustup, in un’altra directory). bin/bz2dec-takt è lo stesso source/src/main.rs collegato alla
build TAKT del crate, che mantiene l’API pubblica, il tipo di errore e i messaggi del crate.
Contenuto
bin/:bz2dec-originalebz2dec-takt;source/: il driver e i file Cargo da cui si compilabz2dec-original;tools/:fetch_silesia.py(corpus e input),run_bench.sh(verifica e misura dei tempi),compare_corrupted.py(stream validi e corrotti);measurements.json,SHA256SUMS,LICENSE.
Il sorgente della build TAKT di bzip2-rs non è pubblicato: consegniamo build.