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
@@ -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}");+ }+ } }