Halve the seeks: eight 32 MiB windows, not sixteen 16 MiB ones

Measured on a real title: the same 256 MiB budget cut into sixteen
windows per extent took 41.8s against the head-first read's 6.6s, because
every jump collapsed the source's read batching into three-sector calls.
Expected observations depend on total bytes read, not on how finely they
are cut, and measured subtitle density (a display set every ~30-50 MB of
clip) makes a 32 MiB window about even money on its own.
This commit is contained in:
Matthew Jackson
2026-08-02 16:55:55 -07:00
parent 28d5897b86
commit abffa4235a
+16 -12
View File
@@ -79,15 +79,17 @@ const STALL_RETRY_LIMIT: u32 = 2;
/// [`plan_windows`]) instead of spent on the title's first 27 seconds. /// [`plan_windows`]) instead of spent on the title's first 27 seconds.
const PROBE_BUDGET_SECTORS: u32 = 131_072; const PROBE_BUDGET_SECTORS: u32 = 131_072;
/// One sample window: ~16 MiB, a whole number of AACS aligned units /// One sample window: ~32 MiB, a whole number of AACS aligned units
/// (8190 = 2730 units). /// (16_383 = 5461 units).
/// ///
/// Sized in PLAYBACK time, not bytes: a window has to be long enough that a /// Sized against MEASURED subtitle density, not guessed: across a sample of
/// dialogue track's display sets are likely to fall inside it. At UHD feature /// feature titles a full dialogue track carries a display set every ~30-50 MB of
/// bitrates 16 MiB is a couple of seconds, and a full subtitle track carries a /// clip, so a 32 MiB window is about even money on its own and the plan's eight
/// display set every few seconds, so each window is a fair coin — which is why /// of them make an observation near-certain. Halving the window and doubling the
/// the plan takes many of them rather than one big one. /// count buys the same expected number of observations for the same bytes — but
const WINDOW_SECTORS: u32 = 8_190; /// twice the seeks, and a measured 6x wall-clock penalty on a source whose
/// read batching collapses after every jump. Fewer, longer windows.
const WINDOW_SECTORS: u32 = 16_383;
/// Floor on a window (2 MiB). A window smaller than this is too short to be /// Floor on a window (2 MiB). A window smaller than this is too short to be
/// likely to contain a display set at all, so it would spend drive time to learn /// likely to contain a display set at all, so it would spend drive time to learn
@@ -96,9 +98,10 @@ const WINDOW_SECTORS: u32 = 8_190;
/// the run) rather than sampling all of them uselessly. /// the run) rather than sampling all of them uselessly.
const MIN_WINDOW_SECTORS: u32 = CHUNK_SECTORS as u32; const MIN_WINDOW_SECTORS: u32 = CHUNK_SECTORS as u32;
/// Most windows spent on a single extent. Past this, extra windows buy /// Most windows spent on a single extent. Past this, extra windows buy no extra
/// diminishing spread for the same bytes. /// expected observations for the same bytes (the expectation depends on total
const MAX_WINDOWS_PER_EXTENT: u32 = 16; /// bytes read, not on how they are cut up) and cost another seek each.
const MAX_WINDOWS_PER_EXTENT: u32 = 8;
// Windows must start (and, so that every chunk inside them does too, be sized) // Windows must start (and, so that every chunk inside them does too, be sized)
// on the AACS aligned-unit grid — same requirement as CHUNK_SECTORS. // on the AACS aligned-unit grid — same requirement as CHUNK_SECTORS.
@@ -2289,7 +2292,8 @@ mod tests {
// Put the small track's handful of display sets inside one sample window, // Put the small track's handful of display sets inside one sample window,
// so it is genuinely OBSERVED (several sets, none forced) and the verdict // so it is genuinely OBSERVED (several sets, none forced) and the verdict
// turns on its shape rather than on having seen nothing. // turns on its shape rather than on having seen nothing.
let window = plan_windows(ext.sector_count, PROBE_BUDGET_SECTORS)[8]; let plan = plan_windows(ext.sector_count, PROBE_BUDGET_SECTORS);
let window = plan[plan.len() / 2];
let mut reader = SyntheticClipReader::new(vec![ let mut reader = SyntheticClipReader::new(vec![
TrackShape { TrackShape {
pid: small, pid: small,