Skip to main content

thread_bench/
thread_bench.rs

1// Dev/test target, not shipped: a panic here IS the diagnostic (that is what an
2// assertion is). The workspace's unwrap/expect/panic denials exist to keep them
3// off the decoder's untrusted-input path, so they are relaxed for this file.
4#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
5// Dev tools also accumulate fields and helpers kept for the NEXT investigation;
6// dead code here is a scratchpad, not a defect.
7#![allow(dead_code, unused)]
8#![allow(clippy::unnecessary_unwrap, clippy::zombie_processes)]
9//! Threading weigh-in: sequential `encode()` vs GOP-parallel `encode_all()`,
10//! same config — byte-identity asserted (compression unchanged BY CONSTRUCTION),
11//! wall speedup reported. Multi-GOP required for parallelism (keyint < frames).
12use rusty_h264_common::types::YuvFrame;
13use rusty_h264_encoder::{Encoder, EncoderConfig, Preset};
14
15fn read_y4m(path: &str, max: usize) -> (usize, usize, Vec<YuvFrame>) {
16    let raw = std::fs::read(path).unwrap();
17    let e = raw.iter().position(|&b| b == b'\n').unwrap();
18    let hdr = std::str::from_utf8(&raw[..e]).unwrap();
19    let (mut w, mut h) = (0usize, 0usize);
20    for t in hdr.split_whitespace() {
21        match t.as_bytes().first() {
22            Some(b'W') => w = t[1..].parse().unwrap(),
23            Some(b'H') => h = t[1..].parse().unwrap(),
24            _ => {}
25        }
26    }
27    let (ys, cs) = (w * h, (w / 2) * (h / 2));
28    let (mut f, mut p) = (Vec::new(), e + 1);
29    while f.len() < max {
30        let Some(r) = raw[p..].iter().position(|&b| b == b'\n') else { break };
31        p += r + 1;
32        if p + ys + 2 * cs > raw.len() { break }
33        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() });
34        p += ys + 2 * cs;
35    }
36    (w, h, f)
37}
38
39fn main() {
40    let path = std::env::args().nth(1).unwrap();
41    let n: usize = std::env::var("TB_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(240);
42    let gop: u32 = std::env::var("TB_GOP").ok().and_then(|v| v.parse().ok()).unwrap_or(30);
43    let (w, h, frames) = read_y4m(&path, n);
44    for (pn, preset) in [("balanced", Preset::Balanced), ("quality", Preset::Quality)] {
45        let mut cfg = EncoderConfig::new(w, h);
46        cfg.qp = 27; cfg.gop_size = gop; cfg.preset = preset;
47        // best-of-3 each arm
48        let mut seq_ms = f64::MAX; let mut seq_out = Vec::new();
49        for _ in 0..3 {
50            let mut enc = Encoder::new(cfg.clone()).unwrap();
51            let t = std::time::Instant::now();
52            let mut o = Vec::new();
53            for f in &frames { o.extend_from_slice(&enc.encode(f)); }
54            seq_ms = seq_ms.min(t.elapsed().as_secs_f64() * 1e3);
55            seq_out = o;
56        }
57        let mut par_ms = f64::MAX; let mut par_out = Vec::new();
58        for _ in 0..3 {
59            let enc = Encoder::new(cfg.clone()).unwrap();
60            let t = std::time::Instant::now();
61            let o: Vec<u8> = enc.encode_all(&frames).unwrap().concat();
62            par_ms = par_ms.min(t.elapsed().as_secs_f64() * 1e3);
63            par_out = o;
64        }
65        assert_eq!(seq_out, par_out, "seq != parallel — compression WOULD be compromised");
66        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);
67    }
68}