Skip to main content

stuffr_cli/bench/
levels.rs

1//! The level table for `stuffr bench`: which codecs are benchmarked and
2//! which levels each one runs.
3//!
4//! A row is always run with `EncodeOpts::level = None`, so it gets the
5//! encoder's own default; `Levels::default` is only the label shown for it.
6
7use stuffr::FormatKind;
8
9/// A codec's level row. `default` is `None` for codecs without levels;
10/// `sweep` is ascending, includes `default`, and is empty for those codecs
11/// (they get one row).
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub struct Levels {
14    pub default: Option<i32>,
15    pub sweep: &'static [i32],
16}
17
18const fn some(default: i32, sweep: &'static [i32]) -> Levels {
19    Levels {
20        default: Some(default),
21        sweep,
22    }
23}
24
25const NONE: Levels = Levels {
26    default: None,
27    sweep: &[],
28};
29
30/// The level row for `format`, or `None` for an unknown format.
31///
32/// Ranges are each codec's `check_encode_opts` range, except zstd, which
33/// sweeps 1..=22 only: its negative "fast" levels reach -131072 and would
34/// swamp the table.
35pub fn levels_of(format: &str) -> Option<Levels> {
36    Some(match format {
37        "gzip" | "zlib" | "deflate" | "xz" | "lzma" | "lzip" => {
38            some(6, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
39        }
40        "bzip2" => some(6, &[1, 2, 3, 4, 5, 6, 7, 8, 9]),
41        "brotli" => some(11, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]),
42        "zstd" => some(
43            3,
44            &[
45                1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
46            ],
47        ),
48        // `compress`'s level is its `maxbits` (9..=16); the default is 16.
49        "compress" => some(16, &[9, 10, 11, 12, 13, 14, 15, 16]),
50        "lz4" | "snappy" => NONE,
51        _ => return None,
52    })
53}
54
55/// Which implementation of `codec` this build benches: `"c"` (a C library)
56/// or `"pure"` (Rust). Only zstd, xz and lzma have a C tier among the
57/// registry's codecs. `zlib-c` and `lz4-c` do not count: they serve the
58/// `gzip` and `lz4` compatibility links, while the gzip, zlib, deflate and
59/// lz4 codecs bench runs stay on flate2's miniz_oxide and on lz4_flex in
60/// every build. Each C tier is this crate's own passthrough feature of the
61/// same name (`lzma-c` is on through `c-backed`, as `xz-c` is).
62pub fn backend_of(codec: &str) -> &'static str {
63    const C_TIER: [(&str, bool); 3] = [
64        ("zstd", cfg!(feature = "zstd-c")),
65        ("xz", cfg!(feature = "xz-c")),
66        ("lzma", cfg!(feature = "lzma-c")),
67    ];
68    if C_TIER.iter().any(|&(name, on)| on && name == codec) {
69        "c"
70    } else {
71        "pure"
72    }
73}
74
75/// Every codec that can be benchmarked: a codec that writes and whose
76/// encoder is not the weak pure-tier one, in capability-matrix order.
77pub fn benchable_codecs() -> Vec<&'static str> {
78    let reg = stuffr::registry();
79    reg.matrix()
80        .into_iter()
81        .filter(|row| row.kind == FormatKind::Codec && row.write)
82        .filter(|row| reg.codec(row.id).is_some_and(|c| !c.caps().weak_encoder))
83        .map(|row| row.id.as_str())
84        .collect()
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use std::io::Write;
91    use std::sync::{Arc, Mutex};
92    use stuffr::{EncodeOpts, FormatId, FormatKind};
93
94    #[derive(Clone)]
95    struct Buf(Arc<Mutex<Vec<u8>>>);
96
97    impl Write for Buf {
98        fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
99            self.0.lock().unwrap().extend_from_slice(b);
100            Ok(b.len())
101        }
102        fn flush(&mut self) -> std::io::Result<()> {
103            Ok(())
104        }
105    }
106
107    fn input() -> Vec<u8> {
108        b"the quick brown fox jumps over the lazy dog. "
109            .iter()
110            .copied()
111            .cycle()
112            .take(4096)
113            .collect()
114    }
115
116    fn encode(name: &'static str, level: Option<i32>) -> Vec<u8> {
117        let codec = stuffr::registry()
118            .codec(FormatId::new(name))
119            .expect("codec registered");
120        let buf = Buf(Arc::default());
121        let opts = EncodeOpts {
122            level,
123            ..EncodeOpts::default()
124        };
125        let mut sink = codec.encoder(Box::new(buf.clone()), &opts).unwrap();
126        sink.write_all(&input()).unwrap();
127        sink.finish().unwrap();
128        buf.0.lock().unwrap().clone()
129    }
130
131    #[test]
132    fn every_benchable_codec_has_a_row() {
133        for name in benchable_codecs() {
134            assert!(levels_of(name).is_some(), "{name} has no level row");
135        }
136        assert!(levels_of("no-such-format").is_none());
137    }
138
139    #[test]
140    fn the_default_label_is_the_encoders_default() {
141        for name in benchable_codecs() {
142            let l = levels_of(name).unwrap();
143            if let Some(d) = l.default {
144                assert_eq!(
145                    encode(name, Some(d)),
146                    encode(name, None),
147                    "{name}: label {d} is not the encoder default"
148                );
149                assert!(l.sweep.contains(&d), "{name}: sweep omits its default");
150            }
151        }
152    }
153
154    #[test]
155    fn every_sweep_level_is_accepted() {
156        for name in benchable_codecs() {
157            let codec = stuffr::registry().codec(FormatId::new(name)).unwrap();
158            let l = levels_of(name).unwrap();
159            assert!(
160                l.sweep.windows(2).all(|w| w[0] < w[1]),
161                "{name}: not ascending"
162            );
163            for &level in l.sweep {
164                let opts = EncodeOpts {
165                    level: Some(level),
166                    ..EncodeOpts::default()
167                };
168                codec
169                    .check_encode_opts(&opts)
170                    .unwrap_or_else(|e| panic!("{name} rejects sweep level {level}: {e}"));
171            }
172        }
173    }
174
175    #[test]
176    fn no_weak_encoder_is_benchable() {
177        let reg = stuffr::registry();
178        let benchable = benchable_codecs();
179        for row in reg.matrix() {
180            if reg.codec(row.id).is_some_and(|c| c.caps().weak_encoder) {
181                assert!(
182                    !benchable.contains(&row.id.as_str()),
183                    "{} has a weak encoder",
184                    row.id.as_str()
185                );
186            }
187        }
188    }
189
190    #[test]
191    fn the_backend_follows_the_build() {
192        // The pure zstd encoder is the weak one: a pure zstd is never
193        // benched, so a benched zstd is the C one.
194        let zstd_weak = stuffr::registry()
195            .codec(FormatId::new("zstd"))
196            .is_some_and(|c| c.caps().weak_encoder);
197        assert_eq!(backend_of("zstd") == "c", cfg!(feature = "zstd-c"));
198        if cfg!(feature = "zstd-c") {
199            assert!(!zstd_weak);
200        }
201        assert_eq!(backend_of("xz") == "c", cfg!(feature = "xz-c"));
202        assert_eq!(backend_of("lzma") == "c", cfg!(feature = "lzma-c"));
203        // Pure in every build, `zlib-c` and `lz4-c` included.
204        for name in [
205            "gzip", "zlib", "deflate", "lz4", "bzip2", "brotli", "snappy", "lzip", "compress",
206        ] {
207            assert_eq!(backend_of(name), "pure", "{name}");
208        }
209    }
210
211    #[test]
212    fn the_backend_follows_each_codecs_own_feature() {
213        for (codec, on) in [
214            ("zstd", cfg!(feature = "zstd-c")),
215            ("xz", cfg!(feature = "xz-c")),
216            ("lzma", cfg!(feature = "lzma-c")),
217        ] {
218            assert_eq!(backend_of(codec), if on { "c" } else { "pure" }, "{codec}");
219        }
220    }
221
222    #[test]
223    fn no_container_is_benchable() {
224        let b = benchable_codecs();
225        for c in ["tar", "zip", "ar", "cpio"] {
226            assert!(!b.contains(&c), "{c} must not be benchable");
227        }
228        for row in stuffr::registry().matrix() {
229            if row.kind == FormatKind::Container {
230                assert!(!b.contains(&row.id.as_str()));
231            }
232        }
233    }
234}