crates/gunnar-delta/tests/latency.rs
//! A small `deltify` must not sleep.
//!
//! `in_parallel_with_slice` runs the caller's periodic closure on a scoped
//! thread that sleeps for whatever that closure returns, and `std::thread::scope`
//! waits for it. So the sleep is not a progress refresh rate; it is a floor on
//! the entire call. gitoxide's own convention is 50 ms, and with it a `deltify`
//! over four small objects took 50 ms of which roughly 49.99 were sleeping.
//!
//! # Why this is a loop, and where the budget comes from
//!
//! A single-call timing assertion tight enough to catch a 50 ms floor ("under
//! 20 ms") goes red whenever the machine is busy, and a flaky test gets
//! deleted. Twenty calls against the floor cannot finish under a second no
//! matter what; twenty calls without it are dominated by thread spawn.
//!
//! The budget is measured, not guessed. On a 32-core Threadripper PRO 3975WX,
//! debug build:
//!
//! | load average | with the 50 ms tick | with a 1 ms tick |
//! |---|---|---|
//! | ~90 | 2190 ms | — |
//! | ~195 | — | 244 / 313 / 390 ms |
//!
//! 700 ms sits below the floor by 3x and above the worst observed non-floor run
//! by nearly 2x, on a box carrying six times its core count. The gap is that
//! wide because the two cases differ in kind, not in degree: one sleeps and one
//! does not.
use std::sync::atomic::AtomicBool;
use std::time::Instant;
use gix_object::Kind;
use gunnar_delta::{deltify, Object, Options};
const CALLS: usize = 20;
#[test]
fn twenty_small_searches_do_not_add_up_to_a_sleep() {
let contents: Vec<Vec<u8>> = (0..4u8)
.map(|k| {
let mut v = vec![0u8; 1_024];
for (i, b) in v.iter_mut().enumerate() {
*b = (i as u8).wrapping_add(k);
}
v
})
.collect();
let objects: Vec<Object<'_>> = contents
.iter()
.enumerate()
.map(|(i, c)| {
let mut id = [0u8; 20];
id[..8].copy_from_slice(&(i as u64).to_be_bytes());
Object {
id: gix_hash::ObjectId::from(id),
kind: Kind::Blob,
name_hash: 0,
epoch: 0,
data: c.as_slice(),
}
})
.collect();
let started = Instant::now();
let mut deltified = 0usize;
for _ in 0..CALLS {
let out = deltify(
&objects,
&Options::default(),
&mut gix_utils::progress::Discard,
&AtomicBool::default(),
)
.expect("deltify");
deltified += out.iter().filter(|d| d.is_delta()).count();
}
let elapsed = started.elapsed();
// The calls must have done real work, or "it was fast" means "it did
// nothing".
assert!(
deltified >= CALLS,
"{deltified} deltas across {CALLS} calls. These searches found nothing, so their \
speed says nothing"
);
assert!(
elapsed.as_millis() < 700,
"{CALLS} searches over four 1 KiB objects took {elapsed:?}. At gitoxide's stock 50 ms \
watcher tick that is exactly {} ms of sleeping, which is what this test exists to \
catch. The work itself is single-digit milliseconds",
CALLS * 50
);
}