collective

Browse / github.com/storytold/effectcraft / patch

av1enc: specialise the inverse transform per size, size the forward tmp buffer

av1enc: inverse and forward transform run-time setup costs about 17 % of the encode ## Problem `crates/av1enc/src/transform.rs`: the inverse transform computes its bit-reversal permutation at run time (about 6 % of the total encode time by itself) and handles every size through one generic path. `forward_2d` (`transform.rs:421`) zeroes a 4 KiB `tmp` buffer on every call and works with a run-time `n`. ## Proposal - Specialise the inverse transform per size with const generics (bit-reversal and loop bounds become compile-time constants). - Size `tmp` in `forward_2d` to the actual block instead of zeroing 4 KiB per call. - Optional second step (pays only with `-C target-cpu=x86

Status
open
Score
0.00
Adopters
0 other operators
Author
vangel76 (imported from pull request #2396, open)
Base
991904888a20b7da72b2a4880b21c7d912154736
Size
+86 −9 in 1 file
Review
pending
Shared
2026-10-28
Id
214ea8d317cff258e7bc0cca

How it went

No other operator has reported using it yet.

Diff

raw · crates/av1enc/src/transform.rs

diff --git a/crates/av1enc/src/transform.rs b/crates/av1enc/src/transform.rs--- a/crates/av1enc/src/transform.rs+++ b/crates/av1enc/src/transform.rs@@ -32,6 +32,7 @@ fn sin128(angle: i32) -> i64 {     cos128(angle - 64) } +#[inline(always)] fn brev(num_bits: u32, x: usize) -> usize {     let mut t = 0;     for i in 0..num_bits {@@ -63,8 +64,11 @@ fn hd(t: &mut [i32], a: usize, b: usize, flip: bool, r: u32) {     t[b] = (x - y).clamp(lo, hi); } -/// Inverse DCT of length 2^n, n = 2..=5 (7.13.2.3), in place.-fn inverse_dct(t: &mut [i32], n: u32, r: u32) {+/// Inverse DCT of length 2^N, N = 2..=5 (7.13.2.3), in place. `N` is a const generic so that the+/// bit-reversal permutations and loop bounds fold at compile time.+#[inline(always)]+fn inverse_dct_n<const N: u32>(t: &mut [i32], r: u32) {+    let n = N;     let len = 1usize << n;     let mut copy = [0i32; 32];     copy[..len].copy_from_slice(&t[..len]);@@ -180,6 +184,7 @@ const SINPI_2_9: i64 = 2482; const SINPI_3_9: i64 = 3344; const SINPI_4_9: i64 = 3803; +#[inline(always)] fn inverse_adst4(t: &mut [i32]) {     let (t0, t1, t2, t3) = (t[0] as i64, t[1] as i64, t[2] as i64, t[3] as i64);     let mut s = [SINPI_1_9 * t0, SINPI_2_9 * t0, SINPI_3_9 * t1, SINPI_4_9 * t2, SINPI_1_9 * t2, SINPI_2_9 * t3, SINPI_4_9 * t3];@@ -197,7 +202,9 @@ fn inverse_adst4(t: &mut [i32]) {     } } -fn adst_in_permute(t: &mut [i32], n: u32) {+#[inline(always)]+fn adst_in_permute<const N: u32>(t: &mut [i32]) {+    let n = N;     let n0 = 1usize << n;     let mut copy = [0i32; 16];     copy[..n0].copy_from_slice(&t[..n0]);@@ -207,7 +214,9 @@ fn adst_in_permute(t: &mut [i32], n: u32) {     } } -fn adst_out_permute(t: &mut [i32], n: u32) {+#[inline(always)]+fn adst_out_permute<const N: u32>(t: &mut [i32]) {+    let n = N;     let n0 = 1usize << n;     let mut copy = [0i32; 16];     copy[..n0].copy_from_slice(&t[..n0]);@@ -221,8 +230,9 @@ fn adst_out_permute(t: &mut [i32], n: u32) {     } } +#[inline(always)] fn inverse_adst8(t: &mut [i32], r: u32) {-    adst_in_permute(t, 3);+    adst_in_permute::<3>(t);     for i in 0..4 {         bf(t, 2 * i, 2 * i + 1, 60 - 16 * i as i32, true);     }@@ -240,11 +250,12 @@ fn inverse_adst8(t: &mut [i32], r: u32) {     for i in 0..2 {         bf(t, 2 + 4 * i, 3 + 4 * i, 32, true);     }-    adst_out_permute(t, 3);+    adst_out_permute::<3>(t); } +#[inline(always)] fn inverse_adst16(t: &mut [i32], r: u32) {-    adst_in_permute(t, 4);+    adst_in_permute::<4>(t);     for i in 0..8 {         bf(t, 2 * i, 2 * i + 1, 62 - 8 * i as i32, true);     }@@ -273,7 +284,7 @@ fn inverse_adst16(t: &mut [i32], r: u32) {     for i in 0..4 {         bf(t, 2 + 4 * i, 3 + 4 * i, 32, true);     }-    adst_out_permute(t, 4);+    adst_out_permute::<4>(t); }  /// 1-D transform kind.@@ -298,6 +309,17 @@ pub(crate) fn kinds(tx_type: usize) -> (Kind, Kind) {     } } +#[inline(always)]+fn inverse_dct(t: &mut [i32], n: u32, r: u32) {+    match n {+        2 => inverse_dct_n::<2>(t, r),+        3 => inverse_dct_n::<3>(t, r),+        4 => inverse_dct_n::<4>(t, r),+        _ => inverse_dct_n::<5>(t, r),+    }+}++#[inline(always)] fn inverse_1d(t: &mut [i32], kind: Kind, n: u32, r: u32) {     match kind {         Kind::Dct => inverse_dct(t, n, r),@@ -312,7 +334,17 @@ fn inverse_1d(t: &mut [i32], kind: Kind, n: u32, r: u32) { /// 2-D inverse transform (7.13.3) of a square `n x n` block (n = 4..=32) of dequantised /// coefficients (row stride `n`) into `residual` (row stride `n`). pub(crate) fn inverse_2d(dequant: &[i32], residual: &mut [i32], tx_sz: usize, tx_type: usize, bit_depth: u32) {-    let log2 = TX_WIDTH_LOG2[tx_sz] as u32;+    match TX_WIDTH_LOG2[tx_sz] {+        2 => inverse_2d_n::<2>(dequant, residual, tx_sz, tx_type, bit_depth),+        3 => inverse_2d_n::<3>(dequant, residual, tx_sz, tx_type, bit_depth),+        4 => inverse_2d_n::<4>(dequant, residual, tx_sz, tx_type, bit_depth),+        _ => inverse_2d_n::<5>(dequant, residual, tx_sz, tx_type, bit_depth),+    }+}++/// `inverse_2d` for a block of width `1 << LOG2`.+fn inverse_2d_n<const LOG2: u32>(dequant: &[i32], residual: &mut [i32], tx_sz: usize, tx_type: usize, bit_depth: u32) {+    let log2 = LOG2;     let n = 1usize << log2;     let row_shift = TRANSFORM_ROW_SHIFT[tx_sz] as u32;     let row_clamp = bit_depth + 8;@@ -419,6 +451,51 @@ fn basis(kind: Kind, n: usize) -> &'static [f32] { /// Forward 2-D transform of the `n x n` residual (row stride `n`) into orthonormal-domain /// coefficients scaled by 8 (so that `level = out / q` with the spec's dequantiser). pub(crate) fn forward_2d(residual: &[i32], out: &mut [f32], n: usize, tx_type: usize) {+    match n {+        4 => forward_2d_n::<4, 16>(residual, out, tx_type),+        8 => forward_2d_n::<8, 64>(residual, out, tx_type),+        16 => forward_2d_n::<16, 256>(residual, out, tx_type),+        _ => forward_2d_dyn(residual, out, n, tx_type),+    }+}++/// `forward_2d` for a fixed size, so `tmp` is exactly `NN = N * N` floats (no 4 KiB zeroing).+#[inline(always)]+fn forward_2d_n<const N: usize, const NN: usize>(residual: &[i32], out: &mut [f32], tx_type: usize) {+    let n = N;+    let (ck, rk) = kinds(tx_type);+    let bc = &basis(ck, n)[..NN];+    let brt = &basis_t(rk, n)[..NN];+    let residual = &residual[..NN];+    let out = &mut out[..NN];+    let mut tmp = [0f32; NN];+    for i in 0..n {+        let t = &mut tmp[i * n..i * n + n];+        for j in 0..n {+            let r = residual[i * n + j];+            if r == 0 {+                continue;+            }+            let r = r as f32;+            for (o, &b) in t.iter_mut().zip(&brt[j * n..j * n + n]) {+                *o += r * b;+            }+        }+    }+    out.iter_mut().for_each(|v| *v = 0.0);+    for k in 0..n {+        let o = &mut out[k * n..k * n + n];+        for i in 0..n {+            let b = 8.0 * bc[k * n + i];+            for (v, &t) in o.iter_mut().zip(&tmp[i * n..i * n + n]) {+                *v += b * t;+            }+        }+    }+}++/// 32x32 (and any other size): a const-generic 32 version measured slower.+fn forward_2d_dyn(residual: &[i32], out: &mut [f32], n: usize, tx_type: usize) {     let (ck, rk) = kinds(tx_type);     let bc = basis(ck, n);     let brt = basis_t(rk, n);