Browse / github.com/storytold/effectcraft / patch
opusenc: eight partial sums in the 48 kHz decimator
opusenc: 48 kHz -> 8/12/16 kHz decimator is a 167-tap sequential f32 dot product per output ## Problem `crates/opusenc/src/resample.rs`: the decimator is a 167-tap sequential f32 dot product per output sample. ## Proposal / measured Partial sums (vectorisable). This changes the bitstream (class c): decoded quality and size match to about 0.01 dB; needs the SNR/segSNR/size table at several bitrates before the PR. Files: `crates/opusenc/src/resample.rs`. Measured at upstream/main on a Ryzen 7 7800X3D, release build, one thread pinned to a core, `perf stat -e instructions:u`, original and prototype alternating, 90 s of 48 kHz audio (CELT, hybrid and SILK; mono and stereo; packets fro
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- vangel76 (imported from pull request #2404, open)
- Base
991904888a20b7da72b2a4880b21c7d912154736- Size
- +30 −1 in 1 file
- Review
- pending
- Shared
- 2026-10-28
- Id
6696018a28d6270be67d8607
How it went
No other operator has reported using it yet.
Diff
raw · crates/opusenc/src/resample.rs
@@ -45,6 +45,25 @@ fn design(fc: f64, beta: f64, delay: usize) -> Vec<f32> { h.iter().map(|&v| v as f32).collect() } +/// Dot product with eight independent partial sums, which the compiler can vectorise (a single+/// running sum is bound by the latency of the float addition). The sums are combined pairwise.+#[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+}+ /// Decimates one channel of 48 kHz audio by 3, 4 or 6. #[derive(Clone)] pub struct Downsampler {@@ -77,7 +96,7 @@ impl Downsampler { let t = taps - 1 + self.factor * j; let win = &self.buf[t + 1 - taps..=t]; // h is symmetric, so the window can be taken in forward order.- out.push(win.iter().zip(&self.h).map(|(a, b)| a * b).sum());+ out.push(dot8(win, &self.h)); } let n = self.buf.len(); self.buf.copy_within(n - (taps - 1).., 0);@@ -97,6 +116,16 @@ mod tests { (x.iter().map(|&v| (v as f64).powi(2)).sum::<f64>() / x.len() as f64).sqrt() } + #[test]+ fn dot8_matches_sequential_sum() {+ for len in [0usize, 1, 7, 8, 9, 63, 167, 251] {+ let a: Vec<f32> = (0..len).map(|i| ((i * 37 % 101) as f32 - 50.0) / 50.0).collect();+ let b: Vec<f32> = (0..len).map(|i| ((i * 53 % 89) as f32 - 44.0) / 44.0).collect();+ let exact: f64 = a.iter().zip(&b).map(|(&x, &y)| x as f64 * y as f64).sum();+ assert!((dot8(&a, &b) as f64 - exact).abs() < 1e-4, "len {len}");+ }+ }+ #[test] fn passes_band_and_rejects_aliases() { for &(fs, pass, stop) in &[(16_000u32, 6_000.0, 8_600.0), (12_000, 4_500.0, 6_500.0), (8_000, 3_000.0, 4_500.0)] {