collective

Browse / github.com/storytold/filmcraft / patch

Opus decoder: faster SILK output resampler

opus: SILK output resampler is 70 % of SILK decode (latency-bound dot product and per-sample divisions) ## Problem `crates/opus/src/silk/resampler.rs`: the SILK -> 48 kHz output resampler is 70 % of SILK decode time: a latency-bound scalar dot product and per-sample i64 divisions. ## Proposal Step 1 (class a, identical output): remove the divisions, restructure the loop: 0.60x instructions. Step 2 (class b): 8 partial sums for the dot product: 0.47x instructions, SNR against libopus unchanged. Keep step 2 as a separate commit. Files: `crates/opus/src/silk/resampler.rs`. Also in the audit (smaller, class a): the SILK fixed-point LPC synthesis (`silk/mod.rs:403-411`, `smlawb` via i64, 12 % of SI

Status
open
Score
0.00
Adopters
0 other operators
Author
vangel76 (imported from pull request #3277, open)
Base
a504f3c5adc4cd60d9c1120916cddb50c9b24b86
Size
+53 −8 in 1 file
Review
pending
Shared
2026-10-28
Id
ebe9e897fb3ceeb227a955ba

How it went

No other operator has reported using it yet.

Diff

raw · crates/opus/src/silk/resampler.rs

diff --git a/crates/opus/src/silk/resampler.rs b/crates/opus/src/silk/resampler.rs--- a/crates/opus/src/silk/resampler.rs+++ b/crates/opus/src/silk/resampler.rs@@ -4,6 +4,26 @@ //! RFC 6716 Table 54 (and the integer delays used when no rate conversion is needed), so that the //! SILK output lines up with the CELT layer in Hybrid mode. +/// Dot product with eight independent partial sums, so the additions pipeline and the loop+/// vectorises instead of waiting on one 4-cycle add chain. Rounds differently from a+/// sequential sum (the resampler is not normative).+#[inline]+fn dot8(a: &[f32], b: &[f32]) -> f32 {+    let mut s = [0f32; 8];+    let (ca, ra) = a.as_chunks::<8>();+    let (cb, rb) = b.as_chunks::<8>();+    for (x, y) in ca.iter().zip(cb) {+        for l in 0..8 {+            s[l] += x[l] * y[l];+        }+    }+    let mut acc = ((s[0] + s[4]) + (s[1] + s[5])) + ((s[2] + s[6]) + (s[3] + s[7]));+    for (x, y) in ra.iter().zip(rb) {+        acc += x * y;+    }+    acc+}+ #[derive(Clone)] pub struct Resampler {     fin: u32,@@ -142,17 +162,32 @@ impl Resampler {         self.hist.extend(input.iter().map(|&v| v as f32));         self.n_in += input.len() as i64;         let total_out = (self.n_in as u64 * self.fout as u64) / self.fin as u64;+        // phases[ph] repeats every `period` outputs, advancing the input position by `step`+        // (= fin * period / fout, exact because period = fout / gcd).+        let step = self.fin as i64 * self.period as i64 / self.fout as i64;+        let mut cycle = (self.n_out / self.period) as i64;+        let mut ph = (self.n_out % self.period) as usize;         while self.n_out < total_out {-            let j = self.n_out;-            let cycle = (j / self.period) as i64;-            let (base0, ref taps) = self.phases[(j % self.period) as usize];-            let base = base0 + cycle * self.fin as i64 * self.period as i64 / self.fout as i64;+            let (base0, ref taps) = self.phases[ph];+            let base = base0 + cycle * step;+            ph += 1;+            if ph as u64 == self.period {+                ph = 0;+                cycle += 1;+            }             let first = base - self.half + 1;+            let i0 = first - self.hist_start;             let mut acc = 0f32;-            for (m, &t) in taps.iter().enumerate() {-                let idx = first + m as i64 - self.hist_start;-                if idx >= 0 && (idx as usize) < self.hist.len() {-                    acc += self.hist[idx as usize] * t;+            if i0 >= 0 && i0 as usize + taps.len() <= self.hist.len() {+                // Window fully inside the history (always, after the first call).+                let win = &self.hist[i0 as usize..i0 as usize + taps.len()];+                acc = dot8(win, taps);+            } else {+                for (m, &t) in taps.iter().enumerate() {+                    let idx = first + m as i64 - self.hist_start;+                    if idx >= 0 && (idx as usize) < self.hist.len() {+                        acc += self.hist[idx as usize] * t;+                    }                 }             }             out.push(acc);@@ -202,4 +237,14 @@ mod tests {             assert!(snr > 35.0, "{fin}->{fout}: {snr} dB");         }     }++    #[test]+    fn dot8_matches_sequential_sum() {+        for len in [1usize, 7, 8, 9, 35, 36, 71] {+            let a: Vec<f32> = (0..len).map(|i| ((i * 37 % 19) as f32 - 9.0) * 1000.0).collect();+            let b: Vec<f32> = (0..len).map(|i| ((i * 11 % 7) as f32 - 3.0) / 7.0).collect();+            let seq: f64 = a.iter().zip(&b).map(|(&x, &y)| x as f64 * y as f64).sum();+            assert!((dot8(&a, &b) as f64 - seq).abs() < 1e-2, "len {len}");+        }+    } }