collective

Browse / github.com/storytold/filmcraft / patch

vp9: best-fit buffer pool, no clearing of recycled strip planes and mode info

vp9: the buffer pool's first-fit hands big buffers to small requests, so bands are reallocated every frame (decoder bug, page faults 184K -> 47K) ## Problem `crates/vp9/src/frame.rs` (`Pool`): first-fit keeps handing large buffers to small requests, so the bands are re-allocated (calloc'd) every frame. At 4K: page faults 184K -> 47K and sys time 0.26 s -> 0.06 s with the fix; user instructions -0.6 %. ## Proposal Best-fit / size-class pool (`Pool::take_any`); `Pool::take` becomes unused (remove it, it would warn as dead code). Document the invariant at `Pool::take_any`: every read sample inside the MI grid is written first (holds per the spec and the fuzz tests). ## Proof A poison build (recycled strip planes filled with 0x3FF and the

Status
open
Score
0.00
Adopters
0 other operators
Author
vangel76 (imported from pull request #3294, open)
Base
a504f3c5adc4cd60d9c1120916cddb50c9b24b86
Size
+33 −20 in 3 files
Review
pending
Shared
2026-10-28
Id
507e4fa74f3b432be30e0a04

How it went

No other operator has reported using it yet.

Diff

raw · crates/vp9/src/decoder.rs crates/vp9/src/frame.rs crates/vp9/src/tile.rs

diff --git a/crates/vp9/src/decoder.rs b/crates/vp9/src/decoder.rs--- a/crates/vp9/src/decoder.rs+++ b/crates/vp9/src/decoder.rs@@ -930,7 +930,8 @@ fn assemble_mi(strips: &mut [Strip], mi_rows: usize, mi_cols: usize, pools: &Poo         let s = &mut strips[0];         return (MiGrid { cols: mi_cols, rows: mi_rows, mi: std::mem::take(&mut s.mi) }, std::mem::take(&mut s.seg_ids));     }-    let mut mi = pools.mi.take(mi_rows * mi_cols, MiInfo::default());+    // Every position is copied from a strip below.+    let mut mi = pools.mi.take_any(mi_rows * mi_cols, MiInfo::default());     let mut seg = vec![0u8; mi_rows * mi_cols];     for s in strips.iter_mut() {         let mw = s.mi_col_end - s.mi_col_start;diff --git a/crates/vp9/src/frame.rs b/crates/vp9/src/frame.rs--- a/crates/vp9/src/frame.rs+++ b/crates/vp9/src/frame.rs@@ -246,30 +246,39 @@ impl<T> Pool<T> { impl<T: Clone> Pool<T> {     /// A buffer of `len` elements with unspecified contents (recycled memory when available;     /// `fill` only initialises fresh memory).+    ///+    /// Invariant for callers: every element that is read must have been written first. For the+    /// sample planes and mode info grids this holds because every sample inside the mode info grid+    /// is predicted before a later stage (loop filter, reference reads, intra edges) reads it, and+    /// every mode info position is written by its tile before it is read. Reads are clamped to+    /// that grid (intra edges to `max_x` / `max_y`, references to the visible size). A change that+    /// reads outside the grid would see stale data from a recycled buffer.     pub fn take_any(&self, len: usize, fill: T) -> Vec<T> {         let found = {             let mut b = self.bufs.lock().unwrap_or_else(std::sync::PoisonError::into_inner);-            b.iter().position(|v| v.len() >= len).map(|i| b.swap_remove(i))-        };-        match found {-            Some(mut v) => {-                v.truncate(len);-                v+            // Best fit by capacity: the first fit handed large buffers to small requests (band+            // scratch rows, chroma bands), and once truncated they never served a large one+            // again, so large requests kept allocating fresh (zeroed) memory.+            let mut best: Option<(usize, usize)> = None;+            for (i, v) in b.iter().enumerate() {+                let c = v.capacity();+                if c >= len && best.is_none_or(|(_, bc)| c < bc) {+                    best = Some((i, c));+                    if c == len {+                        break;+                    }+                }             }-            None => vec![fill; len],-        }-    }--    /// A buffer of `len` copies of `fill` (recycled memory when available).-    pub fn take(&self, len: usize, fill: T) -> Vec<T> {-        let found = {-            let mut b = self.bufs.lock().unwrap_or_else(std::sync::PoisonError::into_inner);-            b.iter().position(|v| v.capacity() >= len).map(|i| b.swap_remove(i))+            best.map(|(i, _)| b.swap_remove(i))         };         match found {             Some(mut v) => {-                v.clear();-                v.resize(len, fill);+                // Contents are unspecified: only memory beyond the current length is written.+                if v.len() >= len {+                    v.truncate(len);+                } else {+                    v.resize(len, fill);+                }                 v             }             None => vec![fill; len],diff --git a/crates/vp9/src/tile.rs b/crates/vp9/src/tile.rs--- a/crates/vp9/src/tile.rs+++ b/crates/vp9/src/tile.rs@@ -129,12 +129,15 @@ impl<'a> TileDecoder<'a> {         let strip_h = sb_rows * 64;         let x0 = mi_col_start * 8;         let uv_len = (strip_w >> ss_x) * (strip_h >> ss_y);-        let planes = [s.pools.samples.take(strip_w * strip_h, 0), s.pools.samples.take(uv_len, 0), s.pools.samples.take(uv_len, 0)];+        // Every sample a later stage reads (inside the mode info grid) is predicted before use, so+        // recycled planes need no clearing.+        let planes = [s.pools.samples.take_any(strip_w * strip_h, 0), s.pools.samples.take_any(uv_len, 0), s.pools.samples.take_any(uv_len, 0)];         let mi_w = mi_col_end - mi_col_start;         let strip = Strip {             mi_col_start,             mi_col_end,-            mi: s.pools.mi.take(mi_w * mi_rows, MiInfo::default()),+            // Written for every position of the grid before it is read.+            mi: s.pools.mi.take_any(mi_w * mi_rows, MiInfo::default()),             seg_ids: vec![0; mi_w * mi_rows],             planes,             strides: [strip_w, strip_w >> ss_x, strip_w >> ss_x],