diff --git a/wai/wai-rs/tests/dash_read_bounds.rs b/wai/wai-rs/tests/dash_read_bounds.rs index 3e9f53320..7f6792913 100644 --- a/wai/wai-rs/tests/dash_read_bounds.rs +++ b/wai/wai-rs/tests/dash_read_bounds.rs @@ -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(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:?}"); }