dash_read_bounds: time the short and long runs in turn, the least of seven each #400

Merged
dcharlot merged 2 commits from fix/dash-bounds-interleave into main 2026-10-21 14:38:59 -04:00

View file

@ -7,7 +7,7 @@
//! compared with each other within a footprint bounded by the document's size: the
//! runs are held as written and compared run by run, never expanded.
//! - Comparing them takes CPU time that does not grow with the segments a run lists.
//! - One element of 40000 attributes is read in time linear in its attributes: the
//! - One element of 32000 attributes is read in time linear in its attributes: the
//! names already read are kept in a set, not scanned again for each attribute.
//!
//! Its own test binary with a single test, so that no other test's allocations land in
@ -91,13 +91,17 @@ fn peak<T>(f: impl FnOnce() -> T) -> (T, usize) {
(out, PEAK.load(Relaxed) - start)
}
/// The CPU time of the best of three runs of `f`.
fn cpu(f: impl Fn()) -> Duration {
(0..3).map(|_| {
/// The least CPU time of `f` and of `g` over seven runs each, taken in turn. On a virtual
/// machine a thread's CPU clock also counts the time its host held the virtual CPU, so a
/// busy host slows every run: taking the two in turn lets a busy spell slow both alike, and
/// the least of each is the run it disturbed least.
fn cpu_pair(f: impl Fn(), g: impl Fn()) -> (Duration, Duration) {
let time = |h: &dyn Fn()| {
let t = now();
f();
h();
now() - t
}).min().expect("three runs")
};
(0..7).map(|_| (time(&f), time(&g))).fold((Duration::MAX, Duration::MAX), |(a, b), (x, y)| (a.min(x), b.min(y)))
}
/// `n` Representations, each a timeline of one `S` of 2^20 segments.
@ -141,13 +145,15 @@ fn an_mpd_is_held_as_written_and_read_in_linear_time() {
// themselves, take about the CPU time of 32 timelines of 2^10; one that steps a
// segment at a time takes about a thousand times as long.
let (few, many) = (bomb_of(32, 1 << 10), bomb_of(32, 1 << 20));
let (t_few, t_many) = (cpu(|| check_dash_adjacent(&few, &few).unwrap()), cpu(|| check_dash_adjacent(&many, &many).unwrap()));
let (t_few, t_many) = cpu_pair(|| check_dash_adjacent(&few, &few).unwrap(), || check_dash_adjacent(&many, &many).unwrap());
assert!(t_many <= t_few * 4 + Duration::from_millis(20), "2^20 segments a timeline took {t_many:?}, 2^10 took {t_few:?}");
// One element of 40000 attributes against one of 10000: linear, so about four times
// the CPU time; a reader that scans the names read so far for each one takes about
// sixteen times. The margin keeps a loaded host from deciding it.
let (short, long) = (wide(10_000), wide(40_000));
// One element of 32000 attributes against one of 1000: linear, so about 32 times the
// CPU time, and up to about three times that again where the larger element's sets
// leave a cache a busy host shares; a reader that scans the names read so far for each
// one takes about a thousand times. The sizes are 32 apart so that no cache effect
// reaches the bound between the two.
let (short, long) = (wide(1_000), wide(32_000));
read_xml(&long).unwrap();
let (t_short, t_long) = (cpu(|| drop(read_xml(&short))), cpu(|| drop(read_xml(&long))));
assert!(t_long <= t_short * 8 + Duration::from_millis(20), "40000 attributes took {t_long:?}, 10000 took {t_short:?}");
let (t_short, t_long) = cpu_pair(|| drop(read_xml(&short)), || drop(read_xml(&long)));
assert!(t_long <= t_short * 256 + Duration::from_millis(20), "32000 attributes took {t_long:?}, 1000 took {t_short:?}");
}