Skip to main content

thread_bench/
thread_bench.rs

1//! Threading weigh-in: sequential `encode()` vs GOP-parallel `encode_all()`,
2//! same config — byte-identity asserted (compression unchanged BY CONSTRUCTION),
3//! wall speedup reported. Multi-GOP required for parallelism (keyint < frames).
4use rusty_h264_common::types::YuvFrame;
5use rusty_h264_encoder::{Encoder, EncoderConfig, Preset};
6
7fn read_y4m(path: &str, max: usize) -> (usize, usize, Vec<YuvFrame>) {
8    let raw = std::fs::read(path).unwrap();
9    let e = raw.iter().position(|&b| b == b'\n').unwrap();
10    let hdr = std::str::from_utf8(&raw[..e]).unwrap();
11    let (mut w, mut h) = (0usize, 0usize);
12    for t in hdr.split_whitespace() {
13        match t.as_bytes().first() {
14            Some(b'W') => w = t[1..].parse().unwrap(),
15            Some(b'H') => h = t[1..].parse().unwrap(),
16            _ => {}
17        }
18    }
19    let (ys, cs) = (w * h, (w / 2) * (h / 2));
20    let (mut f, mut p) = (Vec::new(), e + 1);
21    while f.len() < max {
22        let Some(r) = raw[p..].iter().position(|&b| b == b'\n') else { break };
23        p += r + 1;
24        if p + ys + 2 * cs > raw.len() { break }
25        f.push(YuvFrame { width: w, height: h, y: raw[p..p+ys].to_vec(), u: raw[p+ys..p+ys+cs].to_vec(), v: raw[p+ys+cs..p+ys+2*cs].to_vec() });
26        p += ys + 2 * cs;
27    }
28    (w, h, f)
29}
30
31fn main() {
32    let path = std::env::args().nth(1).unwrap();
33    let n: usize = std::env::var("TB_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(240);
34    let gop: u32 = std::env::var("TB_GOP").ok().and_then(|v| v.parse().ok()).unwrap_or(30);
35    let (w, h, frames) = read_y4m(&path, n);
36    for (pn, preset) in [("balanced", Preset::Balanced), ("quality", Preset::Quality)] {
37        let mut cfg = EncoderConfig::new(w, h);
38        cfg.qp = 27; cfg.gop_size = gop; cfg.preset = preset;
39        // best-of-3 each arm
40        let mut seq_ms = f64::MAX; let mut seq_out = Vec::new();
41        for _ in 0..3 {
42            let mut enc = Encoder::new(cfg.clone()).unwrap();
43            let t = std::time::Instant::now();
44            let mut o = Vec::new();
45            for f in &frames { o.extend_from_slice(&enc.encode(f)); }
46            seq_ms = seq_ms.min(t.elapsed().as_secs_f64() * 1e3);
47            seq_out = o;
48        }
49        let mut par_ms = f64::MAX; let mut par_out = Vec::new();
50        for _ in 0..3 {
51            let enc = Encoder::new(cfg.clone()).unwrap();
52            let t = std::time::Instant::now();
53            let o: Vec<u8> = enc.encode_all(&frames).unwrap().concat();
54            par_ms = par_ms.min(t.elapsed().as_secs_f64() * 1e3);
55            par_out = o;
56        }
57        assert_eq!(seq_out, par_out, "seq != parallel — compression WOULD be compromised");
58        println!("{pn:<9} x{} gop{gop}: seq {seq_ms:.0} ms  parallel {par_ms:.0} ms  speedup {:.2}x  (byte-identical ✓)", frames.len(), seq_ms / par_ms);
59    }
60}