collective

Browse / github.com/storytold/filmcraft / patch

Opus decoder: build the PVQ V(n,k) table once

opus: PVQ decode rebuilds the V(n,k) table for every band; per-frame allocations in the decoder ## Problem `crates/opus/src/celt/bands.rs`: the PVQ decoder builds the full V(n,k) table (u64, saturating) for every band. Plus per-frame heap allocations in the hot path (scratch Vecs in the band code). ## Proposal Build the table once (cached, bounded by the existing limits n <= 176, k <= 128), reuse scratch buffers per decoder. ## Measured (prototype) 0.77x instructions, PCM bit-identical (class a). The garbage-packet fuzz tests stay green. Files: `crates/opus/src/celt/bands.rs`. Measured at upstream/main on a Ryzen 7 7800X3D, release build, one thread pinned to a core, `perf stat -e in

Status
open
Score
0.00
Adopters
0 other operators
Author
vangel76 (imported from pull request #3273, open)
Base
a504f3c5adc4cd60d9c1120916cddb50c9b24b86
Size
+47 −22 in 1 file
Review
pending
Shared
2026-10-28
Id
82260cfc140dd79632723ede

How it went

No other operator has reported using it yet.

Diff

raw · crates/opus/src/celt/bands.rs

diff --git a/crates/opus/src/celt/bands.rs b/crates/opus/src/celt/bands.rs--- a/crates/opus/src/celt/bands.rs+++ b/crates/opus/src/celt/bands.rs@@ -13,23 +13,40 @@ pub fn lcg_rand(seed: u32) -> u32 {     seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223) } +/// Largest band partition (n) and pulse count (k) that can be coded: the last band of a 20 ms+/// frame has 176 bins, and `get_pulses` yields at most 128 pulses.+const PVQ_MAX_N: usize = 176;+const PVQ_MAX_K: usize = 128;+const PVQ_W: usize = PVQ_MAX_K + 1;++/// V(n, k) for n <= PVQ_MAX_N, k <= PVQ_MAX_K, saturated at `u32::MAX`, built once.+///+/// Every (n, k) the bitstream can code has V(n, k) < 2^32 (see `rate::fits_in32`), and the+/// decoder only reads entries V(n', k') <= V(n, k), so the saturation is never observable.+fn pvq_table() -> &'static [u32] {+    static TABLE: std::sync::OnceLock<Vec<u32>> = std::sync::OnceLock::new();+    TABLE.get_or_init(|| {+        let mut v = vec![0u32; (PVQ_MAX_N + 1) * PVQ_W];+        v[0] = 1;+        for n in 1..=PVQ_MAX_N {+            v[n * PVQ_W] = 1;+            for k in 1..PVQ_W {+                let a = v[(n - 1) * PVQ_W + k];+                let b = v[n * PVQ_W + k - 1];+                let c = v[(n - 1) * PVQ_W + k - 1];+                v[n * PVQ_W + k] = a.saturating_add(b).saturating_add(c);+            }+        }+        v+    })+}+ /// Decodes a PVQ codeword of `k` pulses in `n` dimensions; returns the sum of squares.-fn decode_pulses(y: &mut [i32], n: usize, k: usize, dec: &mut RangeDecoder, v: &mut Vec<u64>) -> f32 {-    // Table of V(nn, kk) for nn in 0..=n, kk in 0..=k (saturating).-    let w = k + 1;-    v.clear();-    v.resize((n + 1) * w, 0);-    v[0] = 1;-    for nn in 1..=n {-        v[nn * w] = 1;-        for kk in 1..=k {-            let a = v[(nn - 1) * w + kk];-            let b = v[nn * w + kk - 1];-            let c = v[(nn - 1) * w + kk - 1];-            v[nn * w + kk] = a.saturating_add(b).saturating_add(c);-        }-    }-    let total = v[n * w + k];+fn decode_pulses(y: &mut [i32], n: usize, k: usize, dec: &mut RangeDecoder) -> f32 {+    debug_assert!(n <= PVQ_MAX_N && k <= PVQ_MAX_K);+    let table = pvq_table();+    let vv = |nn: usize, kx: usize| table[nn * PVQ_W + kx] as u64;+    let total = vv(n, k);     let ft = total.min(u32::MAX as u64) as u32;     let mut i = if ft >= 2 { dec.uint(ft) as u64 } else { 0 };     let mut kk = k;@@ -40,7 +57,6 @@ fn decode_pulses(y: &mut [i32], n: usize, k: usize, dec: &mut RangeDecoder, v: &             y[j] = 0;             continue;         }-        let vv = |nn: usize, kx: usize| v[nn * w + kx];         let mut p = (vv(rem - 1, kk) + vv(rem, kk)) / 2;         let neg = i >= p;         if neg {@@ -255,7 +271,6 @@ pub struct BandCtx<'m, 'd, 'b> {     pub seed: u32,     pub disable_inv: bool,     pub avoid_split_noise: bool,-    pub pvq_v: Vec<u64>,     pub iy: Vec<i32>,     pub tmp: Vec<f32>, }@@ -426,7 +441,7 @@ impl BandCtx<'_, '_, '_> {             let mut iy = std::mem::take(&mut self.iy);             iy.clear();             iy.resize(n, 0);-            let ryy = decode_pulses(&mut iy, n, k, self.dec, &mut self.pvq_v);+            let ryy = decode_pulses(&mut iy, n, k, self.dec);             let g = gain / ryy.sqrt();             for (xv, &yv) in x[..n].iter_mut().zip(iy.iter()) {                 *xv = g * yv as f32;@@ -705,7 +720,6 @@ pub fn quant_all_bands(         seed: *seed,         disable_inv,         avoid_split_noise: bb > 1,-        pvq_v: Vec::new(),         iy: Vec::new(),         tmp: Vec::new(),     };@@ -925,6 +939,18 @@ mod tests {         }     } +    #[test]+    fn pvq_table_matches_reference_counts() {+        let table = pvq_table();+        for n in 0..=PVQ_MAX_N {+            for k in 0..=PVQ_MAX_K {+                let want = super::super::rate::pvq_v(n, k).min(u32::MAX as u128) as u32;+                // V(0, k > 0) = 0 and V(n, 0) = 1 in both.+                assert_eq!(table[n * PVQ_W + k], want, "n={n} k={k}");+            }+        }+    }+     #[test]     fn pvq_roundtrip_all_codewords_small() {         use crate::range::enc::RangeEncoder;@@ -939,8 +965,7 @@ mod tests {                     let buf = e.done();                     let mut d = RangeDecoder::new(&buf);                     let mut y = vec![0i32; n];-                    let mut v = Vec::new();-                    let ryy = decode_pulses(&mut y, n, k, &mut d, &mut v);+                    let ryy = decode_pulses(&mut y, n, k, &mut d);                     assert_eq!(y.iter().map(|v| v.unsigned_abs() as usize).sum::<usize>(), k);                     assert_eq!(ryy as i32, y.iter().map(|v| v * v).sum::<i32>());                     assert!(seen.insert(y.clone()), "duplicate codeword n={n} k={k} idx={idx}");